diff --git a/app/api/rss/route.ts b/app/api/rss/route.ts new file mode 100644 index 0000000..2870744 --- /dev/null +++ b/app/api/rss/route.ts @@ -0,0 +1,67 @@ +import { getAllEntries } from "@/lib/changelog"; +import { NextResponse } from "next/server"; + +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3000"; +const FEED_TITLE = "ModelTrace Changelog"; +const FEED_DESCRIPTION = + "Progress notes, architecture decisions, and postmortems for ModelTrace."; + +function escapeXml(str: string): string { + return str + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function toRfc822(isoDate: string): string { + return new Date(isoDate).toUTCString(); +} + +export async function GET() { + const entries = getAllEntries(); + + const items = entries + .map((entry) => { + const link = `${SITE_URL}/changelog/${entry.slug}`; + const categories = [ + ...(entry.milestone ? [`${escapeXml(entry.milestone)}`] : []), + ...entry.tags.map((t) => `${escapeXml(t)}`), + ].join("\n "); + + return ` + + ${escapeXml(entry.title)} + ${escapeXml(link)} + ${escapeXml(link)} + ${escapeXml(entry.summary)} + ${toRfc822(entry.date)} + ${categories} + `; + }) + .join(""); + + const lastBuild = + entries.length > 0 ? toRfc822(entries[0].date) : new Date().toUTCString(); + + const xml = ` + + + ${escapeXml(FEED_TITLE)} + ${escapeXml(SITE_URL)}/changelog + ${escapeXml(FEED_DESCRIPTION)} + en-us + ${lastBuild} + + ${items} + +`; + + return new NextResponse(xml, { + headers: { + "Content-Type": "application/rss+xml; charset=utf-8", + "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400", + }, + }); +} diff --git a/app/changelog/[slug]/page.tsx b/app/changelog/[slug]/page.tsx new file mode 100644 index 0000000..6b2286a --- /dev/null +++ b/app/changelog/[slug]/page.tsx @@ -0,0 +1,110 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { getAllEntries, getEntryBySlug, formatDate } from "@/lib/changelog"; +import type { EntryType } from "@/lib/changelog"; +import { notFound } from "next/navigation"; + +const TYPE_LABELS: Record = { + changelog: "Changelog", + post: "Post", + postmortem: "Postmortem", +}; + +interface Props { + params: Promise<{ slug: string }>; +} + +export async function generateStaticParams() { + const entries = getAllEntries(); + return entries.map((e) => ({ slug: e.slug })); +} + +export async function generateMetadata({ params }: Props): Promise { + const { slug } = await params; + const entry = await getEntryBySlug(slug); + if (!entry) return {}; + return { + title: entry.title, + description: entry.summary, + openGraph: { + title: entry.title, + description: entry.summary, + type: "article", + publishedTime: entry.date, + }, + }; +} + +export default async function ChangelogEntryPage({ params }: Props) { + const { slug } = await params; + const entry = await getEntryBySlug(slug); + if (!entry) notFound(); + + return ( +
+ {/* ── Back navigation ─────────────────────────────────────────────── */} + + ← Changelog + + + {/* ── Post header ─────────────────────────────────────────────────── */} +
+
+ + {TYPE_LABELS[entry.type]} + + {entry.milestone && ( + + {entry.milestone} + + )} + +
+ +

{entry.title}

+

{entry.summary}

+ + {entry.tags.length > 0 && ( +
    + {entry.tags.map((t) => ( +
  • + {t} +
  • + ))} +
+ )} +
+ + {/* ── Post body ───────────────────────────────────────────────────── */} +
+ + {/* ── Footer navigation ───────────────────────────────────────────── */} + +
+ ); +} diff --git a/app/changelog/page.tsx b/app/changelog/page.tsx new file mode 100644 index 0000000..6e0fd85 --- /dev/null +++ b/app/changelog/page.tsx @@ -0,0 +1,137 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { getAllEntries, getMilestones, formatDate } from "@/lib/changelog"; +import type { EntryType } from "@/lib/changelog"; + +export const metadata: Metadata = { + title: "Changelog", + description: + "Progress notes, architecture decisions, and postmortems for ModelTrace — tagged to grant milestones so reports assemble themselves.", +}; + +// Type badge colours match globals.css .cl-type-badge[data-type] +const TYPE_LABELS: Record = { + changelog: "Changelog", + post: "Post", + postmortem: "Postmortem", +}; + +interface Props { + searchParams: Promise<{ milestone?: string }>; +} + +export default async function ChangelogIndexPage({ searchParams }: Props) { + const { milestone: activeMilestone } = await searchParams; + const allEntries = getAllEntries(); + const milestones = getMilestones(); + + const entries = activeMilestone + ? allEntries.filter((e) => e.milestone === activeMilestone) + : allEntries; + + return ( + <> + {/* ── Hero ────────────────────────────────────────────────────────── */} +
+
+
+ Changelog +

Progress, decisions, postmortems.

+

+ Every entry is tagged to a grant milestone so a report can be + assembled from work written as it happened, not reconstructed after + the fact. +

+

+ + + + + + RSS feed + + · + {allEntries.length} entries +

+
+
+ + {/* ── Milestone filter bar ─────────────────────────────────────────── */} + {milestones.length > 0 && ( + + )} + + {/* ── Entry list ──────────────────────────────────────────────────── */} +
+ {entries.length === 0 && ( +

No entries for this milestone yet.

+ )} + {entries.map((entry) => ( +
+
+ + {TYPE_LABELS[entry.type]} + + {entry.milestone && ( + + {entry.milestone} + + )} + +
+ +

+ {entry.title} +

+ +

{entry.summary}

+ + {entry.tags.length > 0 && ( +
    + {entry.tags.map((t) => ( +
  • + {t} +
  • + ))} +
+ )} + + + Read → + +
+ ))} +
+ + ); +} diff --git a/app/compliance/page.tsx b/app/compliance/page.tsx index 6331ec3..38a0072 100644 --- a/app/compliance/page.tsx +++ b/app/compliance/page.tsx @@ -7,6 +7,13 @@ export const metadata: Metadata = { export default function CompliancePage() { return ( +
+ Compliance +

Compliance surface — product definition TBD.

+

+ Scaffold page — replace with production content, data loaders, and analytics. +

+
<>
diff --git a/app/contracts/page.tsx b/app/contracts/page.tsx index ff64af5..e67bfd3 100644 --- a/app/contracts/page.tsx +++ b/app/contracts/page.tsx @@ -12,6 +12,10 @@ export default function ContractsPage() { return (
Contracts +

Contracts surface — product definition TBD.

+

+ Scaffold page — replace with production content, data loaders, and analytics. +

Soroban contracts

Soroban contracts

On-chain modules that power ModelTrace settlement, attestation, and diff --git a/app/docs/page.tsx b/app/docs/page.tsx index 16afc71..68e5fae 100644 --- a/app/docs/page.tsx +++ b/app/docs/page.tsx @@ -12,6 +12,13 @@ export const metadata: Metadata = { export default function DocsPage() { return ( +

+ Documentation +

Technical specs, governance, and integration guides.

+

+ Scaffold page — replace with production content, data loaders, and analytics. +

+
<>
diff --git a/app/globals.css b/app/globals.css index 4bafd6a..746c81f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -285,7 +285,7 @@ a { color: inherit; text-decoration: none; } padding: 24px 0 40px; } -.section h2 { margin-bottom: 10px; } +.section h1, .section h2 { margin-bottom: 10px; } .list { padding-left: 18px; @@ -490,6 +490,414 @@ a { color: inherit; text-decoration: none; } letter-spacing: 0.04em; } +/* ── Prose utility ────────────────────────────────────────────────────────── + * Apply to any long-form content wrapper (compliance, docs, etc.). + * Sets a comfortable measure and the base reading line-height. */ +.prose { + max-width: var(--measure); + line-height: var(--lh-base); +} + +/* ── Changelog ────────────────────────────────────────────────────────────── + * + * Shared tokens: + * --cl-badge-changelog → cool blue + * --cl-badge-post → soft violet + * --cl-badge-postmortem → amber warning + * ─────────────────────────────────────────────────────────────────────────── */ +:root { + --cl-badge-changelog: color-mix(in srgb, var(--accent) 60%, #1a2d45); + --cl-badge-post: color-mix(in srgb, #a78bfa 60%, #1a1235); + --cl-badge-postmortem: color-mix(in srgb, #fbbf24 55%, #231800); + --cl-badge-changelog-text: #b0e8ff; + --cl-badge-post-text: #ddd6fe; + --cl-badge-postmortem-text: #fde68a; +} + +/* Index hero + ─────────────────────────────────────────────────────────────────────────── */ +.cl-index-hero { + position: relative; + overflow: hidden; + padding: 40px 0 32px; +} + +.cl-index-hero-inner { + position: relative; + z-index: 2; + max-width: 640px; +} + +/* RSS hint row beneath the lead paragraph */ +.cl-rss-hint { + display: flex; + align-items: center; + gap: 8px; + font-size: var(--text-sm); + line-height: var(--lh-sm); + color: var(--muted); + margin: 0; +} + +.cl-rss-sep { opacity: 0.4; } + +.cl-rss-link { + display: inline-flex; + align-items: center; + gap: 5px; + color: var(--accent); + font-weight: 500; + transition: opacity 0.15s ease; +} +.cl-rss-link:hover { opacity: 0.75; } + +/* Milestone filter bar + ─────────────────────────────────────────────────────────────────────────── */ +.cl-filter-bar { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 0 0 24px; +} + +.cl-filter-pill { + display: inline-block; + padding: 5px 14px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent); + color: var(--muted); + font-size: var(--text-xs); + line-height: var(--lh-xs); + font-weight: 500; + letter-spacing: 0.04em; + transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease; +} + +.cl-filter-pill:hover { + background: color-mix(in srgb, var(--accent) 10%, transparent); + border-color: color-mix(in srgb, var(--accent) 48%, transparent); + color: var(--text); +} + +.cl-filter-pill--active { + background: color-mix(in srgb, var(--accent) 16%, transparent); + border-color: color-mix(in srgb, var(--accent) 55%, transparent); + color: var(--accent); +} + +/* Entry list + ─────────────────────────────────────────────────────────────────────────── */ +.cl-entry-list { + display: flex; + flex-direction: column; + gap: 16px; + padding-bottom: 56px; +} + +.cl-empty { + color: var(--muted); + font-size: var(--text-sm); + padding: 24px 0; +} + +/* Card + ─────────────────────────────────────────────────────────────────────────── */ +.cl-card { + background: linear-gradient( + 160deg, + color-mix(in srgb, var(--surface) 90%, var(--bg)) 0%, + var(--surface) 100% + ); + border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent); + border-radius: 14px; + padding: 22px 24px 18px; + transition: border-color 0.15s ease, box-shadow 0.15s ease; +} + +.cl-card:hover { + border-color: color-mix(in srgb, var(--accent) 40%, transparent); + box-shadow: 0 4px 24px color-mix(in srgb, var(--accent) 8%, transparent); +} + +.cl-card-meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 10px; +} + +.cl-card-title { + font-size: var(--text-lg); + line-height: var(--lh-lg); + font-weight: var(--fw-lg); + margin: 0 0 8px; +} + +.cl-card-title a { + color: var(--text); + transition: color 0.12s ease; +} +.cl-card-title a:hover { color: var(--accent); } + +.cl-card-summary { + font-size: var(--text-sm); + line-height: var(--lh-sm); + color: var(--muted); + margin: 0 0 14px; + max-width: 72ch; +} + +.cl-read-more { + display: inline-block; + font-size: var(--text-sm); + font-weight: 600; + color: var(--accent); + transition: opacity 0.12s ease; +} +.cl-read-more:hover { opacity: 0.75; } + +/* Type badge + ─────────────────────────────────────────────────────────────────────────── */ +.cl-type-badge { + display: inline-block; + padding: 3px 10px; + border-radius: 999px; + font-size: var(--text-xs); + line-height: var(--lh-xs); + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.cl-type-badge[data-type="changelog"] { + background: var(--cl-badge-changelog); + color: var(--cl-badge-changelog-text); +} + +.cl-type-badge[data-type="post"] { + background: var(--cl-badge-post); + color: var(--cl-badge-post-text); +} + +.cl-type-badge[data-type="postmortem"] { + background: var(--cl-badge-postmortem); + color: var(--cl-badge-postmortem-text); +} + +/* Milestone chip + ─────────────────────────────────────────────────────────────────────────── */ +.cl-milestone-chip { + display: inline-block; + padding: 3px 10px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-2) 40%, transparent); + background: color-mix(in srgb, var(--accent-2) 10%, transparent); + color: var(--accent-2); + font-size: var(--text-xs); + line-height: var(--lh-xs); + font-weight: 600; + letter-spacing: 0.04em; + transition: background 0.14s ease; +} +.cl-milestone-chip:hover { + background: color-mix(in srgb, var(--accent-2) 18%, transparent); +} + +/* Date + ─────────────────────────────────────────────────────────────────────────── */ +.cl-date { + font-size: var(--text-xs); + line-height: var(--lh-xs); + color: var(--muted); + margin-left: auto; +} + +/* Tag list + ─────────────────────────────────────────────────────────────────────────── */ +.cl-tag-list { + display: flex; + flex-wrap: wrap; + gap: 6px; + list-style: none; + padding: 0; + margin: 0 0 14px; +} + +.cl-tag { + padding: 2px 9px; + border-radius: 999px; + background: color-mix(in srgb, var(--muted) 14%, transparent); + border: 1px solid color-mix(in srgb, var(--muted) 22%, transparent); + color: var(--muted); + font-size: var(--text-2xs); + line-height: var(--lh-2xs); +} + +/* ── Full post layout ────────────────────────────────────────────────────── */ +.cl-post { + max-width: 740px; + padding: 32px 0 64px; +} + +.cl-back-link { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: var(--text-sm); + color: var(--muted); + font-weight: 500; + margin-bottom: 28px; + transition: color 0.12s ease; +} +.cl-back-link:hover { color: var(--accent); } + +.cl-post-header { + margin-bottom: 36px; + padding-bottom: 28px; + border-bottom: 1px solid color-mix(in srgb, var(--accent) 18%, transparent); +} + +.cl-post-meta { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 16px; +} + +.cl-post-title { + font-size: clamp(1.5rem, 3.5vw, 2.2rem); + line-height: 1.2; + font-weight: 800; + letter-spacing: -0.025em; + margin: 0 0 14px; +} + +.cl-post-summary { + font-size: var(--text-md); + line-height: var(--lh-md); + color: var(--muted); + margin: 0 0 18px; + max-width: 62ch; +} + +/* Prose body — inherits .prose max-width + line-height */ +.cl-post-body { + color: var(--text); +} + +.cl-post-body h2 { + font-size: var(--text-xl); + line-height: var(--lh-xl); + font-weight: 700; + margin: 36px 0 12px; + padding-bottom: 8px; + border-bottom: 1px solid color-mix(in srgb, var(--accent) 15%, transparent); +} + +.cl-post-body h3 { + font-size: var(--text-lg); + line-height: var(--lh-lg); + font-weight: var(--fw-lg); + margin: 28px 0 8px; +} + +.cl-post-body p { + margin: 0 0 18px; + color: color-mix(in srgb, var(--text) 88%, var(--muted)); +} + +.cl-post-body a { + color: var(--accent); + text-decoration: underline; + text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent); + text-underline-offset: 3px; +} +.cl-post-body a:hover { + text-decoration-color: var(--accent); +} + +.cl-post-body ul, +.cl-post-body ol { + padding-left: 20px; + margin: 0 0 18px; + color: color-mix(in srgb, var(--text) 88%, var(--muted)); +} + +.cl-post-body li { margin-bottom: 6px; } + +.cl-post-body table { + width: 100%; + border-collapse: collapse; + font-size: var(--text-sm); + margin: 22px 0 28px; +} + +.cl-post-body th, +.cl-post-body td { + text-align: left; + padding: 10px 12px; + border-bottom: 1px solid color-mix(in srgb, var(--muted) 22%, transparent); +} + +.cl-post-body th { + color: var(--muted); + font-weight: 600; + border-bottom-color: color-mix(in srgb, var(--accent) 28%, transparent); +} + +.cl-post-body code { + font-family: ui-monospace, "Cascadia Code", "Fira Code", Consolas, monospace; + font-size: 0.875em; + background: color-mix(in srgb, var(--surface) 90%, var(--bg)); + border: 1px solid color-mix(in srgb, var(--muted) 18%, transparent); + border-radius: 5px; + padding: 1px 6px; +} + +.cl-post-body pre { + background: color-mix(in srgb, var(--surface) 70%, var(--bg)); + border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent); + border-radius: 10px; + padding: 18px 20px; + overflow-x: auto; + margin: 0 0 24px; + font-size: var(--text-sm); + line-height: 1.65; +} + +.cl-post-body pre code { + background: none; + border: none; + padding: 0; + font-size: inherit; +} + +.cl-post-body blockquote { + border-left: 3px solid color-mix(in srgb, var(--accent) 55%, transparent); + margin: 0 0 22px; + padding: 8px 0 8px 18px; + color: var(--muted); + font-style: italic; +} + +.cl-post-body hr { + border: none; + border-top: 1px solid color-mix(in srgb, var(--muted) 22%, transparent); + margin: 32px 0; +} + +/* Post footer */ +.cl-post-footer { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 12px; + margin-top: 48px; + padding-top: 24px; + border-top: 1px solid color-mix(in srgb, var(--muted) 18%, transparent); /* === Responsive breakpoints ========================================== * * Until now this sheet had no media queries at all: every layout relied on diff --git a/app/layout.tsx b/app/layout.tsx index 7b85db8..8066db3 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -52,6 +52,17 @@ export const metadata: Metadata = { }, }; +const nav = [ + ["Product", "/product"], + ["Contracts", "/contracts"], + ["Operators", "/operators"], + ["Compliance", "/compliance"], + ["Roadmap", "/roadmap"], + ["Changelog", "/changelog"], + ["Contributors", "/contributors"], + ["Docs", "/docs"], +] as const; + export default function RootLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/app/operators/page.tsx b/app/operators/page.tsx index c04cb93..cb1b8e1 100644 --- a/app/operators/page.tsx +++ b/app/operators/page.tsx @@ -17,6 +17,11 @@ export default function Page() {
Operators

Operator dashboard

+

+ Attestation volume, usage by model, and quota headroom. Every chart on + this page ships its numbers as a table — an auditor asking for the + underlying figures should not have to ask anyone. +

Operator dashboard

Attestation volume, usage by model, and quota headroom. Data is sourced directly from the testnet settlement layer.

diff --git a/app/product/page.tsx b/app/product/page.tsx index 5b0fcba..9fe60b8 100644 --- a/app/product/page.tsx +++ b/app/product/page.tsx @@ -7,6 +7,13 @@ export const metadata: Metadata = { function PlannedTag() { return ( +
+ Product +

Product surface — product definition TBD.

+

+ Scaffold page — replace with production content, data loaders, and analytics. +

+
f.endsWith(".md")) + .sort() + .reverse(); // lexicographic desc → newest slug first + return files.map(parseEntry); +} + +/** Single entry with rendered HTML body. */ +export async function getEntryBySlug( + slug: string +): Promise<(ChangelogEntry & { body: string }) | null> { + const filepath = path.join(CONTENT_DIR, `${slug}.md`); + if (!fs.existsSync(filepath)) return null; + const raw = fs.readFileSync(filepath, "utf8"); + const { data, content } = matter(raw); + const body = await marked(content, { async: true }); + return { + slug, + title: data.title ?? slug, + date: data.date ? String(data.date) : "", + type: (data.type as EntryType) ?? "post", + milestone: data.milestone ?? undefined, + summary: data.summary ?? "", + tags: Array.isArray(data.tags) ? data.tags : [], + body, + }; +} + +/** Unique milestone labels found across all entries, sorted. */ +export function getMilestones(): string[] { + const entries = getAllEntries(); + const set = new Set(); + for (const e of entries) { + if (e.milestone) set.add(e.milestone); + } + return Array.from(set).sort(); +} + +/** Human-readable date string, e.g. "14 Nov 2025". */ +export function formatDate(iso: string): string { + if (!iso) return ""; + const d = new Date(iso); + return d.toLocaleDateString("en-GB", { + day: "numeric", + month: "short", + year: "numeric", + }); +} diff --git a/package-lock.json b/package-lock.json index 7471df2..74d236b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "modeltrace-web", "version": "0.1.0", "dependencies": { + "gray-matter": "^4.0.3", + "marked": "^18.0.11", "@stellar/freighter-api": "^6.0.1", "next": "^15.1.3", "react": "^19.0.0", @@ -4845,6 +4847,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -4911,6 +4926,18 @@ "node": ">=12.0.0" } }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5273,6 +5300,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", + "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -5664,6 +5728,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6104,6 +6177,15 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -6224,6 +6306,18 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/marked": { + "version": "18.0.11", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.11.tgz", + "integrity": "sha512-HnslJfsZkRPBDJRHvVtAaWlZHEpSu7u8LgQuJCELjRKuWR+hpq4A7sLq3p8HaI9ypVoXDXxV34CsQJEe1+J5Aw==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7218,6 +7312,19 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -7468,6 +7575,12 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -7627,6 +7740,15 @@ "node": ">=4" } }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", diff --git a/package.json b/package.json index 4db239a..519c377 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "test:e2e:ui": "playwright test --ui" }, "dependencies": { + "gray-matter": "^4.0.3", + "marked": "^18.0.11", "@stellar/freighter-api": "^6.0.1", "next": "^15.1.3", "react": "^19.0.0",