# Full content corpus ## Markdown code guide URL: https://papyrus.marcelofelix.com/posts/code-demo/ Summary: Markdown route showing Pure-style Astro/Shiki code blocks, callouts, tables, diagrams, and media. Updated: 2026-07-01T13:00:00.000Z Source: src/content/posts/docs/authoring/10-code-demo.md This route is a compact rendering reference for Markdown-heavy posts. It shows how callouts, code fences, tables, diagrams, media, and fallback artifact links look inside the same article layout used by regular Papyrus posts. ## Obsidian callout syntax > [!NOTE] > Notes render as theme-aware callouts while keeping the original Markdown > readable in source form. > [!TIP] > Tips use the same callout component shape with a different token mix. > [!IMPORTANT] > Important callouts use their own icon and color so they are distinct from tips. > [!WARNING] > Warning variants use warning tokens instead of hardcoded colors. > [!CAUTION] > Caution callouts stay readable in light and dark modes. ## Code title ```rust title="src/main.rs" fn main() { println!("papyrus"); } ``` ## Diff fence ```css title="diff.css" .papyrus-card { background: var(--papyrus-panel); /* [!code --] */ background: transparent; /* [!code ++] */ color: var(--papyrus-accent); /* [!code ++] */ } ``` ## Highlighted lines ```c title="highlight.c" #include int main(void) { puts("papyrus"); // [!code highlight] return 0; } ``` ## Console fences ```console site$ pnpm install site# pnpm build site> pnpm preview ``` ## Kamailio fences ```kamailio title="kamailio.cfg" #!KAMAILIO listen=udp:127.0.0.2:5060 loadmodule "sl.so" modparam("sl", "bind_tm", 0) request_route { if (is_method("INVITE")) { xlog("L_INFO", "call from $si to $ru\n"); sl_send_reply("100", "Trying"); } } ``` ## Task list - [x] Keep feature walkthroughs explicit - [ ] Document the source files beside rendered artifacts - [ ] Choose a renderer before embedding external diagram formats ## Table | Feature | Expected behavior | | --- | --- | | Code title | Render a compact title bar | | Diff | Style added and removed lines | | Table | Stay readable without heavy borders | ## Mermaid fence ```mermaid flowchart LR Markdown --> Code Markdown --> Alerts Markdown --> Diagrams ``` ## Image zoom ## Artifact links - [Mermaid source](/demo/theme-flow.mmd "Hydrated Mermaid source") - [PlantUML source](/demo/call-flow.puml "PlantUML source") - [Excalidraw source](/demo/sketch.excalidraw "Excalidraw source") ## Fallback rendering Some GitHub-style or diagram-adjacent formats need a site-owned plugin or renderer. Papyrus keeps the source visible so authors can choose the right integration for their site. ## Generated content structure URL: https://papyrus.marcelofelix.com/posts/content-structure/ Summary: How Papyrus turns nested Markdown folders and folder metadata into a readable public content index. Updated: 2026-07-01T13:10:00.000Z Source: src/content/posts/docs/authoring/11-content-structure.md Use folder metadata when a site needs nested notes, guides, or knowledge-base pages without turning source paths into fragile public URLs. ## What gets generated Papyrus can read a folder tree, combine explicit frontmatter with folder-level labels, and render a public index that still feels hand-authored. The package fixture in `public/demo/content-tree` generates `public/demo/content-structure.md`. ## How to use it Keep canonical slugs in frontmatter when a page has a permanent URL. Use folders for organization, inherited tags, and section labels. This lets authors move files while the published route, RSS item, search result, and AI metadata stay stable. ```console pnpm papyrus-content-outline public/demo/content-tree public/demo/content-structure.md ``` - Use folder names for broad groups such as `guides`, `notes`, or `reference`. - Use an index file or folder metadata to name a section for readers. - Mark draft, private, or internal posts with `hidden: true` when they need a direct route but should stay out of public lists, feeds, sitemaps, search, and AI exports. - Add explicit `robots` frontmatter only when the direct page itself should be blocked from indexing. ## Public boundary User-facing docs now live as regular posts under `src/content/posts/docs`. Development-only notes stay under `.agents/` and are not linked from navigation unless they are intentionally rewritten as user documentation. ## Source artifact The generated Markdown outline remains available as a public fixture at [`/demo/content-structure.md`](/demo/content-structure.md). ## Markdown authoring guide URL: https://papyrus.marcelofelix.com/posts/markdown-feature-sample/ Summary: A practical guide showing how Papyrus renders Markdown, callouts, code, media, diagrams, and source actions. Updated: 2026-06-30T10:00:00.000Z Source: src/content/posts/docs/authoring/12-markdown-feature-sample.md Papyrus keeps authoring close to regular Astro Markdown. A post stays readable as plain text, builds into a clean article page, and can expose source, share, tag, table-of-contents, and metadata features when the site enables them. Use this page as a compact authoring reference when templating a new site. Every section below shows a Markdown feature in the same shape a real post can use. ## Headings # H1 inside content ## H2 inside content ### H3 inside content #### H4 inside content ##### H5 inside content ###### H6 inside content ## Paragraph features Normal text remains readable. **Bold text**, *italic text*, ***bold italic text***, ~~strikethrough text~~, `inline code`, and [normal links](https://astro.build/) all sit cleanly in a paragraph. Autolinks stay visible: https://github.com/marcelofpfelix/papyrus Escaped characters remain literal: \*not italic\* and \`not code\`. ## Images ![Papyrus layout preview](/images/papyrus-layout.svg) ![Papyrus dark mode preview](/images/papyrus-dark.svg) ## Lists Unordered list: - keep the site repo focused on content and configuration - import reusable components from `astro-theme-papyrus` - avoid copying a whole upstream theme into each site - keep overrides small enough to review Ordered list: 1. Write the post in `src/content/posts`. 2. Let Astro build the static route. 3. Use Papyrus layouts for repeated post UI. Nested list: - Theme - layout - post list - prose styles - Site - content - config - minimal pages Task list: - [x] base layout - [x] post layout - [x] search entry point - [ ] graph view ## Table | Feature | Type | Current state | | --- | --- | --- | | RSS | feed | working | | Tags | metadata | route and search filter | | Archive | index | visible when hidden posts exist | | Graph view | data | generated from the AI graph export | Right and center alignment: | Left | Center | Right | | :--- | :---: | ---: | | alpha | beta | 10 | | longer value | middle | 200 | ## Blockquotes > A good theme makes normal markdown readable before it adds more features. Nested quote: > First level > > > Second level ## GitHub alerts > [!NOTE] > Notes are calm and readable. > [!TIP] > Tips stand out without becoming noisy. > [!IMPORTANT] > Important text is easy to scan. > [!WARNING] > Warnings stay visible in both light and dark mode. > [!CAUTION] > Caution blocks keep the page rhythm intact. ## Code Inline code like `pnpm build` keeps paragraph line-height calm. TypeScript with a title and highlighted lines: ```ts title="src/pages/posts/index.astro" {1,4} import { PapyrusBaseLayout, PapyrusPostList } from "astro-theme-papyrus/components"; import { publishedPosts } from "astro-theme-papyrus/utils"; const posts = publishedPosts(await getCollection("posts")); ``` Rust: ```rust title="src/main.rs" {1,9-12} #[derive(Debug, Clone)] struct Repo { owner: String, name: String, } impl Repo { fn slug(&self) -> String { format!("{}/{}", self.owner, self.name) } } fn main() { let repo = Repo { owner: "marcelofpfelix".into(), name: "papyrus".into(), }; println!("{}", repo.slug()); } ``` Diff with add/remove line styling: ```diff title="papyrus.css" - .papyrus-icon-button:hover { - background: var(--papyrus-panel); - border-color: var(--papyrus-accent); - } + .papyrus-icon-button:hover { + background: transparent; + color: var(--papyrus-accent); + } ``` Shell: ```sh pnpm install pnpm build pnpm papyrus-llms src/content/posts public "$SITE_URL" ``` ## Mermaid ```mermaid flowchart LR Site[Astro site] --> Theme[Papyrus] Theme --> Pure[astro-pure] Theme --> Style[Papyrus CSS] Site --> Content[Markdown posts] ``` Diagram source links: [Mermaid source](/demo/theme-flow.mmd "Open the Mermaid source file") [PlantUML source](/demo/call-flow.puml "Open the PlantUML source file") [Excalidraw sketch](/demo/sketch.excalidraw "Open the editable Excalidraw file") ## Link preview Astro The web framework used by this blog and theme wrapper. astro.build ## Footnotes Footnotes are useful for small asides without breaking the main flow.[^1] [^1]: This is a GitHub-style footnote. ## Definition list Papyrus : reusable layouts, components, and CSS Site : content, config, and route composition ## Details Raw HTML details block This checks whether HTML inside markdown keeps spacing and typography. ## Horizontal rule --- The content after the rule stays connected to the rest of the post. ## What this page shows Public posts can double as useful documentation. Readers see how authoring features work, and maintainers get one page that covers headings, prose, images, lists, callouts, code, diagrams, artifact links, link previews, footnotes, definition lists, and raw HTML details. ## Folder tags for nested posts URL: https://papyrus.marcelofelix.com/posts/folder-tags-demo/ Summary: Content-model guide showing stable slugs with automatic tags from source folders. Updated: 2026-06-30T11:00:00.000Z Source: src/content/posts/docs/authoring/13-folder-tags.md This post lives in `src/content/posts/guides/content-model/` to show how a nested source folder can add metadata without changing the public URL. The public URL uses the explicit `slug`, so the route stays stable even if the file moves later. Because it is not hidden, it appears in the normal post list while still keeping a short public route. The generated tags include: - `astro` from frontmatter - `guides` from the parent folder - `content-model` from the nested folder ```console site$ pnpm content:outline site$ pnpm build ``` ## Deploy Papyrus URL: https://papyrus.marcelofelix.com/posts/deploy/ Summary: Static hosting, build settings, metadata URLs, and base-path notes for a Papyrus site. Updated: 2026-07-01T15:00:00.000Z Source: src/content/posts/docs/deploy/30-deploy.md Papyrus builds to static Astro output, so a consuming site can publish the generated `dist` directory to any static host. ## Local preview Use the local development server while editing content, routes, and theme configuration. For a production-shaped preview, build first and serve the generated output. ```console pnpm install pnpm dev pnpm build pnpm preview ``` For a LAN-safe static preview of the built package demo, use the package Makefile: ```sh make serve ``` The default LAN preview URL is `http://192.168.1.102:4326/`. ## Docker preview The Dockerfile is for repeatable local static preview of the package demo. It does not deploy anything. ```sh make docker-build make docker-run ``` Open `http://localhost:4327/`, then stop the container: ```sh make docker-stop ``` ## Static hosting The deployment artifact is the Astro `dist` directory. Cloudflare Pages, Netlify, Vercel static output, GitHub Pages, and any static file server can host it as long as the configured site URL matches the final domain. - Set the production site URL before generating sitemap, robots, RSS, social metadata, and AI indexes. - Keep secret token lookup and host-specific deploy wrappers outside the reusable theme package. - Check `/robots.txt`, `/sitemap-index.xml`, `/rss.xml`, `/search/`, `/posts/`, and `/collections/` after publishing. ## Build settings Most static hosts need only the package manager, build command, output directory, and production URL. Keep the URL in one environment variable so generated metadata agrees across sitemap, robots, RSS, social previews, search, and AI files. ```text package manager: pnpm build command: SITE_URL="https://example.test" pnpm build output directory: dist ``` If the consuming site uses pnpm, keep `minimumReleaseAge` in `pnpm-workspace.yaml` so production builds do not pick up packages published only minutes ago. ## Cloudflare Pages The package repository pins Wrangler as a dev dependency, so no global Wrangler install is required for the demo deploy target. One-time setup: - create or confirm the Cloudflare Pages project - create a token with Cloudflare Pages write access - store the token outside this repo Repeatable deploy command: ```sh CLOUDFLARE_API_TOKEN="$TOKEN" make deploy-demo ``` The target is repeatable because it updates the same Pages project: ```sh make deploy-demo PAGES_PROJECT=papyrus ``` Do not put personal token lookup helpers in this package. Consuming machines can wrap the command locally. ## Subdirectory deploys When the site is published under a base path, route links go through Papyrus base-path helpers or Astro route helpers instead of hardcoded root-relative strings. Asset URLs use the same base-aware helper used by the layout. Configure the Astro `base` option in the consuming site, then keep internal links on helpers such as `withBase()` or `getRelativeLocaleUrl()`. ## After deploy Open the published URL and confirm the generated routes load. At minimum, check that `/robots.txt`, `/sitemap-index.xml`, `/rss.xml`, `/search/`, `/posts/`, and `/collections/docs/` match the configured site URL. ## Release checklist URL: https://papyrus.marcelofelix.com/posts/release-checklist/ Summary: Package metadata, verification, smoke install, and npm publishing checks for Papyrus releases. Updated: 2026-07-14T08:30:00.000Z Source: src/content/posts/docs/deploy/31-release-checklist.md Papyrus publishes as `astro-theme-papyrus`. Do not reserve or publish an empty package name. A release must ship usable exports, docs, styles, scripts, and examples that can be installed by another Astro site. ## Before publish 1. Update `CHANGELOG.md` with the target version and user-facing changes. 2. Confirm `package.json` has the real package name, repository, homepage, bugs URL, keywords, `license`, `files`, public `publishConfig`, exports, and bin entries. 3. Confirm `pnpm-workspace.yaml` keeps the mature-release install gate: `minimumReleaseAge: 10080` and `minimumReleaseAgeStrict: true`. This blocks registry packages published in the last seven days, including transitive dependencies. 4. Run the focused release gate: ```sh pnpm run verify:release ``` 5. Run the broader package gate: ```sh pnpm run verify:package pnpm run verify:readme pnpm run build ``` 6. For a commit/release gate only, run Lighthouse: ```sh pnpm run verify:lighthouse ``` 7. Smoke install the packed tarball in a clean temporary Astro fixture before publishing. The fixture should import `astro-theme-papyrus/components`, `astro-theme-papyrus/config`, and `astro-theme-papyrus/papyrus.css`. 8. Publish only from an npm account with 2FA enabled. Keep `publishConfig.provenance` enabled so supported CI releases attach npm provenance. ## Pack check `pnpm run verify:release` runs `npm pack --dry-run --json` with a temporary npm cache, then checks that the tarball includes `README.md`, `LICENSE`, `CHANGELOG.md`, public exports, scripts, examples, styles, and release docs. It also packs a real tarball, installs it in a temporary Astro fixture, imports Papyrus components/config/styles from that tarball, and runs `astro build`. The temporary fixture also uses the seven-day pnpm mature-release gate so release smoke installs do not accidentally accept freshly published registry packages. It does not publish, create a git tag, or mutate the npm registry. ## Feature map URL: https://papyrus.marcelofelix.com/posts/feature-map/ Summary: Route-by-route guide to public Papyrus features. Updated: 2026-07-01T14:00:00.000Z Source: src/content/posts/docs/references/20-feature-map.md Every public feature has a route that shows how a consuming site can use it. Start here when exploring Papyrus. ## Feature map - [Install and configure Papyrus](/collections/docs/install-configure-papyrus/) covers the template-first setup flow. - [Layout, header, footer, and theme controls](/) are visible on the home page. - [Post layout and actions](/metadata-demo/) show covers, dates, tags, reading time, adjacent links, source/copy/share actions, table of contents, and optional back links. - [Markdown authoring guide](/collections/docs/code-demo/) shows Pure-style Astro/Shiki code blocks, alerts, task lists, tables, Mermaid SVG, artifacts, and zoom hooks. - [Posts index and tags](/posts/) show pinned ordering, list-only archive view, tag links, compact metadata, timeline link, and small covers. - [Search and tag filters](/search/) work with tag filters, standalone tag pages, and generated RSS feeds for individual tags. - [Collections](/collections/docs/collections/) explains folder-backed post collections detected from TOML files. - [CV/profile templates](/collections/docs/profile/) use normalized CV data across resume, timeline, projects, A4 links, and print routes. - [Site config](/collections/docs/site-config/) explains site identity, navigation, projects, feature flags, and post-list defaults. - [Projects and GitHub cards](/projects/) show pinned projects, project list cards, and Pure-inspired GitHub repository preview cards. - [Feature flags and plugin contract](/collections/docs/features/) document configurable feature flags, comments, plugins, and typed plugin configuration. - [Content structure helpers](/collections/docs/content-structure/) explain nested folders, folder metadata, folder-derived tags, stable slugs, and generated content outlines. - [Deploy and local preview](/collections/docs/deploy/) covers local dev, production preview, static hosting, base-path deploys, and deploy guidance. - [AI and mobile readiness](/collections/docs/ai-mobile/) covers generated metadata, AI indexes, responsive primitives, and mobile authoring guidance. ## How to use it Use this map as an orientation page when adding Papyrus to another Astro site. Each linked route shows the content, component, or generated artifact in context. Repository-only implementation notes stay outside visitor-facing docs. ## AI and mobile readiness URL: https://papyrus.marcelofelix.com/posts/ai-mobile/ Summary: AI metadata, generated indexes, and mobile-ready defaults in a Papyrus site. Updated: 2026-07-01T14:10:00.000Z Source: src/content/posts/docs/references/21-ai-mobile.md Papyrus ships useful agent-readable metadata, generated indexes, and responsive UI defaults for small content sites. ## AI-first primitives - Package-level `llms.txt` and `llms-full.txt` generation for agent-readable site summaries. - Static RSS feeds per tag. - Static JSON indexes for posts, tags, projects, notes, and CV summary data. - Static graph data export connecting posts, tags, projects, notes, and CV sections. - Stable machine IDs in generated JSON indexes, graph nodes, graph edges, and search records. - Reusable static search index for local agents. - Per-post JSON-LD metadata for schema.org types. - Post layout support for canonical URL, source Markdown URL, author metadata, copy Markdown, and copy citation. Generated files include [`llms.txt`](/llms.txt), [`llms-full.txt`](/llms-full.txt), [`posts.json`](/ai/posts.json), [`tags.json`](/ai/tags.json), [`cv.json`](/ai/cv.json), and [`graph.json`](/ai/graph.json). ## Site-specific AI extensions - Add extra source links when a post depends on diagrams, notebooks, or external datasets. - Add JSON-LD types for people, projects, software source code, or breadcrumbs on site-specific pages. - Generate backlinks or related posts from Markdown links when the content model needs a knowledge graph. - Store explicit license metadata for images, code snippets, and diagrams when licensing matters. - Publish diagram source beside rendered SVG output for Mermaid, PlantUML, or Excalidraw workflows. ## Mobile-first primitives - Safe-area padding for phones with notches or rounded display edges. - 44px coarse-pointer hit targets for core icon, copy, post action, tag, TOC, and back-to-top controls. - Responsive post lists with stable cover dimensions and compact metadata. - Collapsible table of contents and back-to-top controls designed for small screens. - Image, SVG, and Mermaid zoom behavior that works with touch input. - Reduced-motion handling for cursor, logo, header, dropdown, and page-transition effects. - Profile and CV routes that share one data source across web, timeline, and print layouts. - Static Pagefind search and generated tag pages, so mobile search does not require a server. ## Mobile authoring tips Keep post descriptions short enough to scan in list views, prefer real cover images or generated covers with stable aspect ratios, use headings in order, and preview layout-heavy changes on desktop and phone-sized screens before publishing. ## Dark mode and search in Papyrus URL: https://papyrus.marcelofelix.com/posts/dark-mode-and-search/ Summary: The theme keeps a Pure-like neutral background, then adds a small Papyrus-style search flow. Updated: 2026-06-30T11:15:00.000Z Source: src/content/posts/docs/references/22-dark-mode-and-search.md The light and dark backgrounds stay close to the default Pure theme: neutral, quiet, and readable. Papyrus keeps color accents restrained so the content remains the main surface. The header has a search icon. It links to the site-owned `/search/` page. That keeps the package generic: Papyrus exposes the header control and search UI patterns, while the consuming site decides which index to build. The public Papyrus site builds a Pagefind index from generated static HTML. A smaller site can still use a simple client-side filter, but the public contract stays the same: keep the search URL stable and let the site own the indexing strategy. ```astro --- import { PapyrusBaseLayout } from "astro-theme-papyrus/components"; ---

The header search icon opens the site-owned search page.

``` That is the important boundary. The icon and layout affordance belong to the theme. The index strategy belongs to the site. ## Papyrus package shape URL: https://papyrus.marcelofelix.com/posts/papyrus-package-shape/ Summary: Package-boundary guide showing how Papyrus keeps repeated blog UI in reusable theme exports. Updated: 2026-06-30T11:00:00.000Z Source: src/content/posts/docs/references/24-papyrus-package-shape.md Papyrus is meant to keep the site repo focused. The consuming site owns content, configuration, route choices, and deployment. The theme package owns reusable UI: base layout, post layout, header, footer, post lists, tags, archive helpers, profile/CV components, generated metadata helpers, and shared CSS. ![Papyrus centered layout](/images/papyrus-layout.svg) A content site usually needs the same core pieces on several routes: - a clean post list - tags and tag pages - timeline or archive page - RSS feed - reading time - previous and next post links - light and dark mode - a search entry point in the header - generated metadata for RSS, sitemap, robots, search, and AI indexes The package boundary stays explicit. Papyrus reuses Pure as the foundation, adds Papyrus-style publishing features, exposes a stable public API through package exports, and lets the site override content, config, and routes. ```ts import { PapyrusBaseLayout, PapyrusPostList } from "astro-theme-papyrus/components"; import { publishedPosts } from "astro-theme-papyrus/utils"; ``` That is the intended shape: small imports, low local code, and theme updates handled in one package. ## Post list views The public posts page uses the standard list view so the archive stays easy to scan: ```astro title="src/pages/posts/index.astro" ``` The same component can still render denser or card-like post groups inside documentation references, landing pages, or custom sections: ```astro title="post-list-view-reference.astro" ``` Keeping these alternatives inside a post leaves `/posts/` focused on the main archive while documenting every supported display mode where readers can inspect it as content. ## RSS feed choices The normal feed at `/rss.xml` is the broad subscription. Tag feeds are separate URLs, so a reader can subscribe only to the topics they want instead of filtering after the fact: - [`/rss/tags/custom.xml`](/rss/tags/custom.xml) follows posts tagged with custom authoring features. - [`/rss/tags/changelog.xml`](/rss/tags/changelog.xml) follows release and package-change notes. That is the practical RSS model for selective subscriptions: publish one feed per tag or site-owned section, then document the important subscription choices inside the relevant post. ## Collections URL: https://papyrus.marcelofelix.com/posts/collections/ Summary: What Papyrus collections are and how folder-backed collection routes work. Updated: 2026-07-13T08:10:00.000Z Source: src/content/posts/docs/references/25-collections.md A collection is a folder of posts with a TOML file that describes the public collection page. It is useful when readers need to follow posts in a deliberate order instead of the normal date-based blog order. The same Markdown post can appear in the regular `/posts/` archive and in a collection route. The archive sorts by publishing metadata; the collection sorts by filename so authors can control the sequence. ## Folder shape Create a folder under `src/content/posts` and add a TOML file inside it: ```txt src/content/posts/ docs/ docs.toml start/ 00-papyrus-docs.md 03-site-config.md references/ 25-collections.md ``` Papyrus detects TOML files below `src/content/posts`. The collection slug comes from the folder name, so `src/content/posts/docs/docs.toml` becomes `/collections/docs/`. ## Collection config ```toml title="src/content/posts/docs/docs.toml" name = "Papyrus docs" description = "Package docs for installing, composing, and extending Papyrus." [settings] post_footer = "collection" post_footer_collapsible = true [[sections]] name = "Start" description = "Getting started and configuration." [[sections]] name = "References" description = "Reusable route and content references." ``` Sections are matched by folder slug. A section named `Start` reads posts from `start/`; a section named `References` reads posts from `references/`. ## Ordering Collection pages use filename order, not post date order. Prefix filenames when the order matters: ```txt start/00-papyrus-docs.md start/01-features.md start/02-install-configure-papyrus.md start/03-site-config.md ``` Keep explicit `slug` frontmatter when moving a post into a collection. That preserves the normal `/posts/my-slug/` route while also adding the collection route at `/collections/docs/my-slug/`. ## Post pages inside a collection Collection post pages receive collection-aware navigation: - the back link points to the collection page - previous and next links follow filename order - the table of contents can include collection sections - the footer can show the current collection, collapsed by default Use collection routes when context and sequence matter. Use normal post routes when date order and discovery are the main concern. ## Hiding collection posts `hidden: true` keeps a post reachable by direct URL but removes it from public collection lists, feeds, tag pages, sitemap, search, and AI indexes. Use it for fixtures and private-ish drafts that should not be part of public discovery. ## Profile and CV URL: https://papyrus.marcelofelix.com/posts/profile/ Summary: What the Papyrus profile data file controls and how profile, print, and export routes share it. Updated: 2026-07-13T08:20:00.000Z Source: src/content/posts/docs/references/26-profile.md The profile system is driven by `src/data/profile.toml`. The same source data is used by the web profile page, timeline view, print routes, generated Markdown, and generated JSON. Keep personal data in this TOML file instead of hardcoding it in components. That makes a consuming site easier to template, review, export, and print. ## User fields ```toml title="src/data/profile.toml" [user] name = "Site Author" avatar = "images/avatar.svg" bio = "Writer and software engineer" url = "site.test" print_color = "#37474F" email_user = "hello" email_domain = "site.test" links = ["linkedin", "github"] print_links = ["email", "linkedin", "github", "website"] location = "Lisbon, Portugal" born = "1991-04-18T00:00:00" roles = ["Software Engineer", "Technical writer"] sections = ["about", "experience", "education", "skills"] ``` Use `email_user` and `email_domain` instead of a single literal email address when you want the page to assemble the visible contact with less obvious static scraping. It is not cryptographic protection; it only avoids the most basic email harvesters. ## Links Each named link can define a display name, base URL, and icon: ```toml title="src/data/profile.toml" [user.github] name = "site-owner" url = "https://github.com/" icon = "github" [user.linkedin] name = "site-owner" url = "https://www.linkedin.com/in/" icon = "linkedin" ``` The web profile can show a broader set of links through `links`. Print routes can use a smaller set through `print_links`. ## Sections Sections are declared in `[user.data.*]`. They can represent about text, experience, education, skills, interests, projects, or any site-specific CV group that follows the same structure. ```toml title="src/data/profile.toml" [user.data.experience] title = "Professional Experience" icon = "briefcase" page = 1 groups = ["company"] [user.data.experience.range] a = "2022-01-01T00:00:00" b = "2026-07-01T00:00:00" [user.data.experience.company] ententy = "Company" url = "https://example.com/" items = ["role"] [user.data.experience.company.role] title = "Senior Engineer" dates = "2022 - Present" location = "Remote" description = "Built and maintained content-heavy web systems." [user.data.experience.company.role.range] a = "2022-01-01T00:00:00" ``` Date ranges are used for calculated age and experience durations. Items without dates can still appear in the profile, but timeline-style views should only show dated material. ## Skills Skills can be written as tags and prose. Tag-style skills can link naturally to tag pages when the site uses matching post tags. ```toml title="src/data/profile.toml" [user.data.skills.skills.skills] tags = ["astro", "markdown", "theme", "search"] description = """-- Astro content collections and static builds.
-- Markdown rendering, callouts, code blocks, and diagrams.""" ``` ## Export and print Run the export command after editing profile data: ```sh pnpm run cv:export ``` The command writes `public/cv/profile.json` and `public/cv/profile.md`. The web routes use the TOML source, while the generated artifacts are useful for sharing, source actions, and external review. The profile page links to print routes. The modern print route and classic ATS route should use the same normalized data so the site does not maintain multiple resumes by hand. ## Theme specification URL: https://papyrus.marcelofelix.com/posts/theme-spec/ Summary: Public behavior contract for Papyrus features, configuration, posts, search, profile, SEO, accessibility, and quality gates. Updated: 2026-07-14T08:00:00.000Z Source: src/content/posts/docs/references/27-theme-spec.md This document defines the public behavior contract for `papyrus`. It is intended for two audiences: - maintainers reviewing whether the implementation still matches the agreed theme behavior - users creating a site from the theme and deciding which features to enable, customize, or replace The request audit remains the internal implementation tracker. This file is the public-facing product specification. ## Goals `papyrus` is a reusable Astro theme package for content-heavy personal sites, blogs, profiles, and technical notes. The theme MUST: - stay a thin wrapper around Pure where Pure already provides a strong pattern - keep consuming sites focused on content, configuration, and small page composition - provide reusable layout, post, search, RSS, profile, CV, markdown, and SEO behavior from the package - remain fast, accessible, keyboard-friendly, SEO-friendly, and suitable for static hosting - support generated machine-readable artifacts for search engines, AI tools, feed readers, and source reviewers The theme SHOULD: - prefer existing upstream behavior from Pure before inventing a local version - prefer simple static output over client-heavy runtime code - keep public UI calm, readable, and compact - avoid visual borders and heavy card nesting unless a component genuinely needs a framed surface - make configuration explicit instead of hardcoding site-specific behavior ## Package boundary The package owns: - base and post layouts - shared post-list components - post metadata, source, copy, share, TOC, and adjacent-post controls - profile and CV components - markdown rendering behavior - theme tokens and theme/font controls - search, tag, archive, RSS, sitemap, robots, and AI index helpers - demo pages and reusable documentation Consuming sites own: - content files - site identity, social links, profile data, and project data - deployment configuration - site-specific page composition - private or production-only data Shared behavior MUST move into `papyrus` when more than one consuming site would otherwise copy it. ## Configuration Site behavior MUST be configurable through site-owned config/data rather than hardcoded inside components. The supported post-card configuration includes: - `tags`: whether the post list shows clickable tag rows - `read_time`: whether the post list shows reading time - `fresh_indicators`: whether new or updated posts show an indicator - `fresh_indicator_text`: whether the new-post indicator includes a text label - `updated_date_only`: whether list cards prefer the updated date over the original published date Home and `/posts/` MUST use the same post-list configuration path so those pages do not drift. The post list MUST support a configurable default count. The package default is 20 posts for `/posts/`. Back links and other post controls MUST be configurable. A consuming site MAY disable the post back link. Google Site Verification MUST be configurable from site config and rendered as a standard `google-site-verification` meta tag when provided. Base-path and subdirectory deployments MUST be supported. Internal links and asset links SHOULD go through shared helpers so a site can deploy under a path such as `/astro-papyrus` without manual link rewrites. ## Home page and posts index Home and `/posts/` MUST render posts through the same shared post-list component. Differences between the two pages should be limited to input data, limit, heading, and local page composition. The post list MUST support: - list view - compact view - card view - pinned posts - optional cover images - title - short description - date metadata - optional read-time metadata - optional tags - optional first-tag display when full tag rows are hidden - optional new indicator with text - accent-colored date metadata for recently updated posts Pinned posts MUST be visually distinguishable from normal posts. The pinned state should use a subtle background related to project-card surfaces and an icon without requiring label text. When `post_card.tags` is false: - home and `/posts/` MUST both hide the normal tag row - the first tag MAY appear inline with the date as plain, non-clickable context - the behavior MUST be consistent between home and `/posts/` When `post_card.read_time` is false: - reading time MUST be hidden from post-list cards - the post page MAY still show reading time if the post-page metadata feature is enabled Dates in list and post metadata MUST use the compact public date style: - current-year dates omit the year - non-current-year dates include the year first - the month uses the short English month name - examples: `Jul 09`, `2025 Jul 09` Reading time labels MUST use compact text such as `1 min`, not `1 min read`. The `/posts/` page MUST expose icon links for related post browsing modes: - timeline - tags - archive, when hidden posts exist - RSS, using an icon next to the posts heading or equivalent title area These icon links SHOULD be borderless, aligned to the right where the layout allows, and use the same icon set and visual treatment across consuming sites. The `/posts/` page SHOULD avoid exposing multiple RSS examples in page chrome. Specialized RSS feeds, such as tag-specific feeds or changelog feeds, should be documented inside a demo post instead. ## Timeline and archive The timeline MUST group posts by year. By default it SHOULD show the current year and previous year expanded, while older years are collapsed. Future implementations MAY lazy-load older year groups when the number of posts makes the full timeline expensive. The archive view MUST expose hidden posts when hidden posts exist. Hidden posts are defined by post frontmatter, not by file location. Archive links SHOULD be presented as an icon action from `/posts/`, not as a primary navigation item. ## Tag and search behavior Tags MUST support dedicated `/tag//` pages for public post browsing. Search MUST support text search and tag filtering. A search URL MAY use query parameters such as `?tag=astro` for filtered search results. Tag links in public post metadata SHOULD point to tag pages when the tag page exists. Tags that do not have a public tag page, such as note-only tags, MAY fall back to search-filter URLs. Nested post folders SHOULD contribute automatic folder tags. Explicit frontmatter tags remain supported, and duplicates MUST be removed. Pagefind integration MUST be complete for searchable public pages. Pages marked `noindex` or otherwise excluded from public indexing SHOULD not be indexed by Pagefind. ## Post page The post page header MUST prioritize reading: - title centered and larger than surrounding controls - short description centered and smaller than the title - sufficient vertical spacing between the title block and the post body - optional cover image above the post when configured The top of the post MAY include a configurable back link. When enabled, the link SHOULD be named `Back`, use a back arrow, and avoid colored button backgrounds. Post metadata, tags, source/copy/share actions, and top-link controls SHOULD live near the bottom of the article rather than above the reading content. Immediately after article content ends, the page SHOULD show: - a divider or line break - a `Back` link to the post index, using a back arrow and text - source/copy/share controls on the same line when space allows The source/copy/share controls MUST: - use consistent icon and text labels: `Copy`, `Source`, `Share` - use a copy icon for the copy action - avoid excess spacing between icon and text - avoid background-color treatments that do not match other icon links - use the same visual style across all posts The share action MUST use the Web Share API when available. It MUST fall back to the existing copy-link behavior when native sharing is unavailable or cannot share the current page. The source action MAY link to a GitHub source URL, JSON source, TOML source, or Markdown source when configured. The copy action for post source MUST copy Markdown content when Markdown source is available. Post tags MUST appear near the bottom of the post. Tag links SHOULD use `/tag//` where supported. The date line MUST stay close to the tags/actions area without creating a large vertical gap. The previous and next post links MUST be adjacent navigation, not primary page actions. On mobile, previous MUST align left and next MUST align right. The oldest and newest posts MUST still render the appropriate one-sided adjacent link when one adjacent post exists. Adjacent post data SHOULD be computed once in `getStaticPaths` and passed into the post layout. A post page SHOULD NOT fetch every post again just to compute previous and next links. Post pages SHOULD expose: - a floating table-of-contents button when headings exist - a back-to-top button on desktop, aligned with the metadata/tools row where appropriate - a top progress bar on mobile while scrolling through a post The table-of-contents control MUST not wrap the `On this page` label on desktop. On mobile, the table-of-contents and back-to-top controls MUST use matching touch-target size and visual style. The table-of-contents open state SHOULD feel close to Pure: large enough to be usable, but not so large that it consumes most of the page. ## Markdown and prose Markdown rendering MUST support: - Shiki/Pure-style code blocks - titles and language labels for code blocks - line numbers where configured - diff and highlight notation - code-copy buttons using Pure behavior - Mermaid fenced blocks rendered as diagrams - task lists - readable tables without boxed borders - image and SVG zoom behavior - artifact links for supported external files - GitHub-style alert syntax through the maintained callout pipeline GitHub-style alert/callout colors MUST distinguish variants: - tip: green - important: blue - warning: amber - caution: red Important and caution callouts MUST not reuse the exact same icon. Warning and caution MUST also be visually distinguishable. Unsupported markdown features SHOULD remain visibly documented in demos instead of silently pretending to work. ## Theme and visual system Theme profiles MUST be token-driven CSS files with light and dark variants. Every theme profile MUST define: - `--papyrus-bg` - `--papyrus-fg` - `--papyrus-muted` - `--papyrus-panel` - `--papyrus-border` - `--papyrus-accent` - `--papyrus-code-bg` - `--papyrus-code-fg` - `--papyrus-theme-color` - font tokens The default package theme MAY differ from a consuming site's default theme. Consuming sites MUST be able to set their own default theme profile and font profile. Footer controls MUST expose: - mode control - theme profile control - font profile control Theme controls SHOULD be icon-first, compact, keyboard-accessible, and use dropdowns that open inside the viewport. The Gruvbox dark profile MUST use the updated accessible colors: - `#f5edca` instead of the old `#d4be98` - `#c4bda1` instead of the old `#928374` - `#dfb778` instead of the old `#d8a657` Icon links such as timeline, tags, archive, source, copy, share, and RSS SHOULD be borderless and avoid unnecessary background colors. Post cards SHOULD have no borders. Pinned post cards SHOULD keep a distinct surface background. Links in dense text areas SHOULD remain accessible without making the layout visually noisy. Bolder text and hover underlines are preferred over permanent underlines when contrast and affordance remain acceptable. ## Profile and CV The profile system MUST use data-driven content. Links, emails, social accounts, location, role, title, sections, projects, and timelines MUST come from TOML, JSON, Astro content, or another site-owned data source, not from hardcoded component values. TOML is the preferred source format for bundled demos and simple consuming-site configuration. The normalized profile/CV model MUST support: - name - title or role - photo/avatar - location and other personal information - email parts - social links - section groups - professional experience - education and other CV sections - profile projects - printable pages Social links SHOULD use the same icon source as other theme icons where possible. Social labels should display the username or handle when available, not only the network name. Email display MUST avoid publishing a plain email address in the static HTML when the site chooses obfuscation. The supported public-data strategy is to store email parts such as user and server separately, optionally render a visual separator such as an `@` symbol, and assemble the real address only when needed. The goal is to avoid common low-effort scraping while accepting that determined parsers can reconstruct public parts. When printing or exporting a CV, the generated print view SHOULD be able to render the real email address if the site has enough public data to assemble it. The web profile page SHOULD center the photo, name, and title inside the profile summary box. The print CV view MUST be papyrus-like, centered, black-and-white friendly, and easy for recruiting software to parse. It SHOULD avoid decorative graphics, complex columns, low-contrast text, and link styling that makes the print output look uneven. The print view SHOULD: - use primary color for icons - keep left and right side treatment visually similar - remove unnecessary blue/bold link styling from usernames - keep social and personal-info spacing compact - provide print/save, share, copy, and source actions with icon styling - support JSON, TOML, and Markdown source links when configured Age and professional-experience duration calculations MUST be derived from data, not hardcoded. Calculations SHOULD follow the jekyllcv-style behavior used by the demo comparison. The package SHOULD provide a way to generate Markdown and JSON versions of the CV into the repository. This can be wired into a commit hook by the consuming site, but the package should not mutate files during a normal build unless the site explicitly opts in. ## SEO, sharing, and indexing The theme MUST be SEO-friendly by default. Every public page SHOULD provide: - title - description - canonical URL - robots metadata when configured - Open Graph metadata - social sharing metadata - heading hierarchy without skipped levels - semantic HTML landmarks Pages shared to social media SHOULD render useful previews through properly configured Open Graph and social metadata, including preview images when available. Cover images and generated preview images SHOULD be supported for posts and profile pages. Missing preview images should be treated as a regression for public pages that advertise share previews. The sitemap MUST be generated dynamically so every public page, post, tag page, category-like page, and generated route can be included without manual updates. `robots.txt` MUST be generated dynamically. It MUST include the sitemap URL and allow sites to control search-engine indexing and crawling policy from config. The theme SHOULD expose machine-readable artifacts for AI and search tooling, including: - `llms.txt` - JSON indexes - search indexes - graph data - stable canonical IDs - post source links - citation/source actions ## RSS The theme MUST provide a main RSS feed. The theme MUST support multiple RSS feeds, including tag-specific feeds. A site MAY expose custom feeds such as a changelog feed. RSS examples SHOULD be documented in content rather than overloading the main posts-page UI. Users who want to subscribe to only some tags SHOULD be able to use tag-specific feed URLs. ## 404 behavior The theme MUST provide a real 404 page, not a blunt server error page. The 404 page SHOULD: - explain that the page was not found - offer search - automatically suggest similar public pages based on title, path, description, and tags - link to common recovery destinations such as posts, search, docs, projects, or profile when available The 404 page MUST work as a static route for static hosting. ## Accessibility The theme MUST be fully keyboard navigable. Interactive controls MUST have: - reachable focus states - accessible names - label-in-name alignment where applicable - useful `aria` attributes where native HTML is not enough - touch targets suitable for mobile Images MUST have meaningful `alt` text or be marked decorative when appropriate. Headings MUST not skip levels in normal page flow. Tables, callouts, forms, search, TOC, theme controls, copy buttons, share buttons, and CV controls MUST remain understandable without a mouse. The theme SHOULD preserve Lighthouse accessibility score 100 unless a documented, reviewed exception is accepted. ## Performance and quality gates The theme SHOULD keep Lighthouse scores at 100 for performance, accessibility, best practices, SEO, and agentic browsing on all generated public pages. Lighthouse is intentionally slow and SHOULD be run before commits or release review, not after every small edit. The default Lighthouse minimum score is 100. Route exclusions MUST be explicit and justified. Focused validation SHOULD be used during development: - docs checks for documentation changes - content checks for post/date/tag/routing changes - browser checks for UI behavior - responsive checks for layout and touch-target changes - Lighthouse before commits or release gates No feature should be marked complete only because the site builds. Visual behavior, clipboard behavior, Mermaid rendering, print/A4 layout, and responsive layout require targeted evidence. ## Public demo expectations The demo site MUST illustrate the enabled package features using safe example content. The public demo SHOULD include examples for: - markdown/code/callout behavior - posts, tags, timeline, archive, and search - profile and CV data sources - print CV routes - RSS and tag-specific RSS - SEO and metadata behavior - 404 suggestions - theme and font controls - AI/search artifacts Internal request trackers and status audits SHOULD stay repository-only unless explicitly published. ## Review checklist Use this checklist when reviewing whether code still respects the spec: - Home and `/posts/` use the same post-list component and config path. - Post list cards honor tag, read-time, fresh-indicator, pinned, and date configuration consistently. - Post pages keep reading content first and move metadata/actions/tags toward the bottom. - Share uses native Web Share when available and copy-link fallback otherwise. - Source/copy/share controls use consistent icon styling and labels. - Previous and next links are ordered correctly and align left/right on mobile. - Tags route to `/tag//` where supported. - Search and Pagefind indexes include the expected public pages. - Hidden posts are reachable through archive behavior when configured. - 404 is a real page with search and suggestions. - Sitemap and robots are generated and include canonical sitemap references. - SEO/social metadata exists on public pages. - Markdown callouts have distinct colors and icons. - Theme tokens, including Gruvbox dark colors, match the spec. - Profile and CV pages are data-driven and do not hardcode personal links. - Obfuscated email output avoids plain static email HTML when configured. - Print CV remains papyrus-like, readable, compact, and parser-friendly. - Accessibility and keyboard behavior remain intact. - Lighthouse minimum remains 100 before commit/release gates. ## Papyrus docs URL: https://papyrus.marcelofelix.com/posts/papyrus-docs/ Summary: Package docs for installing, composing, and extending Papyrus without copying a full theme into every site. Updated: 2026-07-01T09:00:00.000Z Source: src/content/posts/docs/start/00-papyrus-docs.md Papyrus keeps consuming sites focused on content and config. The theme package owns reusable layouts, post rendering, collections, generated metadata, search, RSS, project cards, and profile/CV surfaces. ## Start points - [Install and configure Papyrus](/collections/docs/install-configure-papyrus/) explains the template-first workflow. - [Feature config](/collections/docs/features/) shows grouped layout and post toggles. - [Site config](/collections/docs/site-config/) explains `papyrus.config.toml`. - [Collections](/collections/docs/collections/) explains folder-backed collection routes. - [Profile and CV](/collections/docs/profile/) explains `src/data/profile.toml`. - [Feature map](/collections/docs/feature-map/) maps the public routes to reusable theme features. - [Markdown code guide](/collections/docs/code-demo/) shows Markdown, code, callouts, diagrams, media, and artifacts. ## Theme profiles Each theme profile defines one light token set and one dark token set. Runtime mode defaults to system, while the default color profile is Gruvbox and the default font profile is readable. Included profiles are Catppuccin, Tokyo Night, Kanagawa, Rose Pine, Everforest, Dracula, Gruvbox, Nord, and Pure. ```css :root[data-papyrus-theme="custom"] { --papyrus-bg: #fbf7ef; --papyrus-fg: #1f2933; --papyrus-muted: #6b7280; --papyrus-panel: #ffffff; --papyrus-border: #d8d0bf; --papyrus-accent: #7c3aed; --papyrus-code-bg: #f4efe5; --papyrus-code-fg: #111827; --papyrus-theme-color: #fbf7ef; } ``` Use `pnpm papyrus-theme list` to inspect installed profiles and `pnpm papyrus-theme validate` to check that every profile file has the required light and dark selectors and tokens. ## Content model Papyrus uses `src/content/posts` as the public content source. A folder can add a TOML file to become a collection, and the collection route reads posts in filename order. Regular blog/archive pages can still sort the same posts by date. Repo-only notes stay under `.agents/` unless they are intentionally rewritten as public posts. ## Feature config URL: https://papyrus.marcelofelix.com/posts/features/ Summary: Grouped feature toggles for Papyrus layouts. Updated: 2026-07-01T09:10:00.000Z Source: src/content/posts/docs/start/01-features.md Use one typed config object to disable optional layout and post features without copying theme components. ## Base layout ```astro --- import { PapyrusBaseLayout } from "astro-theme-papyrus/components"; const features = { header: true, footer: true, scrollHeader: true, search: false, rss: false, themeControls: true, poweredBy: true, }; ---

No search icon and no RSS icon on this page.

``` ## Post layout ```astro --- import { PapyrusPostLayout } from "astro-theme-papyrus/components"; const features = { share: false, toc: false, postTags: true, aiMetadata: true, sourceActions: false, postStats: false, postSideLinks: false, adjacentPosts: false, backToTop: false, }; ---

Rendered without share, TOC, source actions, stats, or adjacent links.

``` ## SEO and social metadata Papyrus layouts emit the metadata a small public site normally needs: titles, descriptions, canonical URLs, Open Graph, Twitter cards, RSS discovery, and post JSON-LD. Hidden posts stay out of public discovery surfaces. Use explicit `robots` frontmatter when a direct page should emit directives such as `noindex, follow`. ## Base path deploys Internal URLs in shared layouts and list components go through `withBase()` and related helpers such as `stripBase()`, `stripLocale()`, `getAssetPath()`, and `getRelativeLocaleUrl()`. This keeps navigation, post links, RSS links, favicon assets, covers, tags, collection links, adjacent posts, project cards, and 404 suggestions working when Astro is deployed under a subdirectory. ## Search, tags, sitemap, and robots Public posts can be found through Pagefind search, tag pages, and optional per-tag RSS feeds. Regenerate the sitemap during every build so new posts, tag pages, profile pages, project pages, and collection routes are included automatically. Papyrus also includes a dynamic `robots.txt` route that reads the site URL, keeps crawl rules near site config, and points crawlers to the sitemap index. ## Plugin contract Community plugins stay small: they declare capabilities, optional feature defaults, and site-owned integration points. They do not mutate Papyrus internals. Routes stay native Astro pages in the consuming app unless a plugin explicitly documents a route capability. ## Install and configure Papyrus URL: https://papyrus.marcelofelix.com/posts/install-configure-papyrus/ Summary: Use papyrus-template as the recommended starting point, then configure the site with TOML, Markdown, and asset overrides. Updated: 2026-07-10T09:00:00.000Z Source: src/content/posts/docs/start/02-install-configure-papyrus.md The recommended way to start a Papyrus site is to use `papyrus-template`. The template keeps site-owned files small: one site config, one profile config, one example post, and a few asset overrides. Papyrus provides the layouts, standard pages, content collection, RSS route, robots route, post UI, profile components, and theme CSS through the published `astro-theme-papyrus` package. ## Start from the template Create a new repository from `papyrus-template`, then install dependencies: ```sh pnpm install pnpm dev ``` The package repo uses pnpm's mature-release guard: `minimumReleaseAge: 10080` and `minimumReleaseAgeStrict: true`. That seven-day gate makes installs fail instead of silently falling back when a dependency was published too recently. The template depends on the npm package: ```json title="package.json" { "dependencies": { "astro-theme-papyrus": "^0.2.0" } } ``` The template enables Papyrus in Astro: ```js title="astro.config.mjs" import sitemap from "@astrojs/sitemap"; import { defineConfig } from "astro/config"; import { loadPapyrusConfig } from "astro-theme-papyrus/config"; import papyrus from "astro-theme-papyrus/integration"; const papyrus = await loadPapyrusConfig(); export default defineConfig({ site: papyrus.site, integrations: [papyrus(), sitemap()], }); ``` It also reuses the Papyrus content collection: ```ts title="src/content.config.ts" export { collections } from "astro-theme-papyrus/content"; ``` Papyrus injects these standard routes from the package: | Route | Source | | --- | --- | | `/` | Home page with latest posts and project cards | | `/posts/` | Public post list | | `/posts/[...slug]/` | Post detail page | | `/projects/` | Project cards from `papyrus.config.toml` | | `/profile/` | Profile page from `src/data/profile.toml` | | `/tag/` and `/tag/[tag]/` | Tag index and tag detail pages | | `/404.html` | Helpful not-found page | | `/rss.xml` | Main RSS feed | | `/robots.txt` | Robots file with sitemap URL | Because the pages are injected by the package, the template does not need a `src/pages` tree. ## Know `src` vs `public` Papyrus follows the same Astro convention used by Papyrus-style starter repos: `src/` is source, `public/` is static output input. Use `src/` for files Astro should read, transform, type-check, or route during the build: - `src/content/posts/*.md` for posts - `src/content.config.ts` for the Papyrus content collection export - `src/data/profile.toml` for the profile and CV source - `src/pages/*.astro` only when a consuming site needs custom routes that the package does not already provide Use `public/` for files that should be copied to the deployed site as-is: - `public/logo.svg`, `public/favicon.svg`, and `public/site.webmanifest` - `public/images/*` for covers, avatars, and project images referenced by frontmatter or `papyrus.config.toml` - generated artifacts such as `public/cv/profile.json`, `public/cv/profile.md`, `public/ai/*`, `public/rss/tags/*`, and `public/pagefind/*` Do not hand-edit generated files in `public/`. Edit the source in `src/`, `papyrus.config.toml`, or `src/data/profile.toml`, then regenerate the artifacts. ## Edit the right file For normal site work, start with these files: | Goal | Edit | | --- | --- | | Site title, description, nav, social links, projects, theme, feature flags, and post-card defaults | `papyrus.config.toml` | | Add or edit posts | `src/content/posts/*.md` | | Add a page that Papyrus does not already inject | `src/pages/*.astro` | | Change the profile, CV, links, skills, dates, and print color | `src/data/profile.toml` | | Change logos, favicons, covers, avatars, and project images | `public/` assets | The package repo has many files because it owns reusable components, injected routes, scripts, and demo fixtures. A consuming site should stay closer to the template shape: config, content, profile data, and assets. Long-lived site copy should live in Markdown, TOML, JSON, or other site-owned data. Components may keep generic fallback labels such as `Back`, `Share`, `Source`, or `On this page`, but site-specific text should not be hidden inside package code. Demo routes in this repository can contain route-local prose when they are examples; if that prose becomes user documentation, move it into a post or docs content source. ## Edit the site config Most site behavior starts in `papyrus.config.toml`. The template keeps the system color preference as the default and uses the Everforest theme profile. ```toml title="papyrus.config.toml" [site] title = "My site" description = "Notes, projects, and profile." url = "https://site.test" lang = "en" dir = "ltr" timezone = "Europe/Lisbon" [brand] title = "My site" mark = "twinkle" show_title = true [theme] profile = "everforest" font_profile = "readable" [features] graph = false search = false comments = false postStats = false [post_card] tags = true read_time = true fresh_indicators = true fresh_indicator_text = false updated_date_only = true limit = 20 [[nav]] href = "/posts/" label = "Posts" [[nav]] href = "/projects/" label = "Projects" [[nav]] href = "/profile/" label = "Profile" [[project]] title = "My site" description = "The site built from papyrus-template." href = "/posts/welcome/" image = "/images/cover.svg" repo = "https://github.com/site-owner/site" pinned = true status = "active" [[project.links]] href = "https://github.com/site-owner/site" label = "repo" text = "site-owner/site" ``` Use exactly the project cards you want to publish. The starter keeps two projects to demonstrate local and upstream links without turning the template into a demo catalog. ## Add posts Posts live in `src/content/posts`. A minimal post needs a title, description, date, and optional tags. ```md title="src/content/posts/publishing-with-papyrus.md" --- title: Publishing with Papyrus description: A short implementation note published from a Papyrus-powered site. pubDatetime: 2026-07-10T09:00:00.000Z tags: [astro, papyrus] cover: /images/cover.svg --- Write the post body in Markdown. ``` Papyrus handles list pages, detail pages, adjacent post links, tags, reading time, cover images, RSS entries, and the generated route paths. ## Edit the profile The profile page reads `src/data/profile.toml`. ```toml title="src/data/profile.toml" [user] name = "Site Author" title = "Software Engineer" bio = "Writer and software engineer" location = "Lisbon, Portugal" print_color = "#37474F" email_user = "hello" email_domain = "site.test" sections = ["about", "experience", "education", "skills"] ``` The same profile data can be exported to JSON and Markdown with: ```sh pnpm run cv:export ``` ## Override assets Keep visual identity in `public/`: - `public/logo.svg` - `public/favicon.svg` - `public/site.webmanifest` - `public/images/cover.svg` - `public/images/avatar.svg` - project images referenced by `papyrus.config.toml` These are normal site assets. Papyrus supplies the components and theme CSS, while the consuming site supplies its own images and metadata. ## Advanced composition The template is the recommended path. For a custom site that needs different routes, import Papyrus components directly: ```astro --- import { PapyrusBaseLayout, PapyrusPostList } from "astro-theme-papyrus/components"; import { publishedPosts, routablePosts } from "astro-theme-papyrus/utils"; const allPosts = await getCollection("posts"); const posts = publishedPosts(allPosts); const routePosts = routablePosts(allPosts); const adjacentPosts = publishedPosts(allPosts); --- ``` Use `routePosts` when creating static post detail paths. Use `adjacentPosts` when computing previous/next links so hidden archive pages can still exist as routes without showing up in public navigation. In a post-detail route, pass folder-aware tags into the post layout: ```astro ``` Keep the boundary clear: Papyrus owns reusable pages, components, styles, and helpers. The consuming site owns content, TOML config, asset overrides, private data, analytics, and deployment settings. ## Site config URL: https://papyrus.marcelofelix.com/posts/site-config/ Summary: What papyrus.config.toml controls and how a site should edit it. Updated: 2026-07-13T08:00:00.000Z Source: src/content/posts/docs/start/03-site-config.md `papyrus.config.toml` is the site-level control file. It keeps the consuming site editable without copying theme code into `src/pages` or component files. Use it for public site identity, navigation, social links, project cards, theme defaults, feature flags, and post-list behavior. Use frontmatter for per-post metadata, and use `src/data/profile.toml` for profile and CV data. ## What it controls | Section | Purpose | | --- | --- | | `[site]` | Site title, description, URL, language, text direction, and timezone | | `[brand]` | Header brand title, mark, and whether the text title is visible | | `[theme]` | Default color profile and font profile | | `[features]` | Optional UI, metadata, comments, search, graph, media, and profile features | | `[post_card]` | Post-list tags, read time, fresh indicators, updated-date behavior, and default limit | | `[[nav]]` | Header navigation links | | `[[social]]` | Social links used by header/footer surfaces | | `[[project]]` | Project cards shown by the project list components | Papyrus reads this file with `loadPapyrusConfig()`. If the file is missing, Papyrus falls back to package defaults so a minimal template can still build. ## Minimal config ```toml title="papyrus.config.toml" [site] title = "My site" description = "Notes, projects, and profile." url = "https://site.test" lang = "en" dir = "ltr" timezone = "Europe/Lisbon" [brand] title = "My site" mark = "twinkle" show_title = true [theme] profile = "everforest" font_profile = "readable" [[nav]] href = "/posts/" label = "Posts" [[nav]] href = "/profile/" label = "Profile" ``` Keep the URL set to the deployed origin. The same value is used for canonical metadata, RSS, sitemap, robots, social previews, and generated AI indexes. ## Feature flags Feature flags are booleans. Set only the flags you want to override: ```toml title="papyrus.config.toml" [features] search = true comments = false postStats = false graph = false ``` The common rule is to disable features that need external setup. For example, turn off comments and remote post stats until the consuming site has configured those services. ## Post-list defaults Post-card options apply to normal post lists such as home and `/posts/`: ```toml title="papyrus.config.toml" [post_card] tags = false read_time = false fresh_indicators = true fresh_indicator_text = false updated_date_only = true limit = 20 ``` When `tags = false`, the first tag can still be shown as plain context in the date line. When `updated_date_only = true`, list cards show the update date for updated posts while the post page can still show both created and updated dates. ## Projects and links Use repeated TOML tables for navigation, social links, and projects: ```toml title="papyrus.config.toml" [[social]] href = "https://github.com/site-owner" label = "GitHub" icon = "github" [[project]] title = "My project" description = "A short public project summary." href = "/projects/my-project/" image = "/images/project.svg" repo = "https://github.com/site-owner/project" pinned = true status = "active" [[project.links]] href = "https://github.com/site-owner/project" label = "repo" text = "site-owner/project" ``` Use `public/` for images referenced by config. Use `src/` for content and data that Astro should parse or transform. ## Related files - `astro.config.mjs` wires the Papyrus integration and loads this config. - `src/content.config.ts` exports the Papyrus content collection. - `src/data/profile.toml` owns profile, CV, timeline, print, and source export data. - `src/content/posts/**` owns posts, docs, and collection content. ## Choco Frito URL: https://papyrus.marcelofelix.com/posts/choco-frito/ Summary: Setubal-style fried cuttlefish strips served with lemon, fries, and salad. Updated: 2024-10-20T09:00:00.000Z Source: src/content/posts/food/fish/00-choco-frito.md Choco Frito is crisp fried cuttlefish, strongly associated with Setubal. ## Ingredients - Cuttlefish strips - Garlic, bay leaf, and lemon - Flour or cornmeal - Oil for frying - Fries and salad ## Method Season the cuttlefish, coat lightly, fry until crisp, and serve with lemon and simple sides. ## Bacalhau a Bras URL: https://papyrus.marcelofelix.com/posts/bacalhau-a-bras/ Summary: Shredded salt cod folded with onions, matchstick potatoes, eggs, olives, and parsley. Updated: 2024-01-20T09:00:00.000Z Source: src/content/posts/food/fish/01-bacalhau-a-bras.md Bacalhau a Bras is one of the most familiar Portuguese cod dishes. ## Ingredients - Desalted shredded cod - Onion and garlic - Matchstick potatoes - Eggs - Black olives and parsley ## Method Soften the onion, add cod and potatoes, then fold in beaten eggs off direct heat so the mixture stays creamy. ## Ameijoas a Bulhao Pato URL: https://papyrus.marcelofelix.com/posts/ameijoas-a-bulhao-pato/ Summary: Clams cooked quickly with garlic, olive oil, white wine, coriander, and lemon. Updated: 2024-09-20T09:00:00.000Z Source: src/content/posts/food/fish/02-ameijoas-a-bulhao-pato.md Ameijoas a Bulhao Pato is a fast clam dish with a bright garlic-coriander sauce. ## Ingredients - Fresh clams - Garlic - Olive oil - White wine - Coriander and lemon ## Method Cook garlic in olive oil, add clams and wine, cover until opened, then finish with coriander and lemon. ## Bacalhau com Natas URL: https://papyrus.marcelofelix.com/posts/bacalhau-com-natas/ Summary: Salt cod baked with potatoes, onion, cream, and a golden breadcrumb top. Updated: 2024-02-20T09:00:00.000Z Source: src/content/posts/food/fish/03-bacalhau-com-natas.md Bacalhau com Natas is a creamy baked cod dish with potatoes and onion. ## Ingredients - Desalted cod flakes - Potatoes - Onion and garlic - Cream or bechamel - Breadcrumbs ## Method Layer cod, potatoes, and onion in a baking dish, cover with cream sauce, and bake until bubbling and golden. ## Cataplana de Peixe URL: https://papyrus.marcelofelix.com/posts/cataplana-de-peixe/ Summary: Fish and shellfish steamed with tomato, pepper, onion, herbs, and white wine. Updated: 2024-08-20T09:00:00.000Z Source: src/content/posts/food/fish/04-cataplana-de-peixe.md Cataplana de Peixe uses a sealed pan to steam fish with vegetables and wine. ## Ingredients - Firm white fish - Clams or prawns - Tomato, pepper, onion, and garlic - White wine - Coriander and olive oil ## Method Layer everything in the cataplana, close it, and cook until the fish is just done and the broth is aromatic. ## Bacalhau a Gomes de Sa URL: https://papyrus.marcelofelix.com/posts/bacalhau-a-gomes-de-sa/ Summary: Oven-baked cod with potatoes, onions, eggs, olives, parsley, and olive oil. Updated: 2024-03-20T09:00:00.000Z Source: src/content/posts/food/fish/05-bacalhau-a-gomes-de-sa.md Bacalhau a Gomes de Sa is a Porto cod classic with potatoes and plenty of olive oil. ## Ingredients - Desalted cod - Potatoes - Onion and garlic - Hard-boiled eggs - Olives, parsley, and olive oil ## Method Bake cod, sliced potatoes, and onions together, then finish with eggs, olives, parsley, and more olive oil. ## Polvo a Lagareiro URL: https://papyrus.marcelofelix.com/posts/polvo-a-lagareiro/ Summary: Tender octopus roasted with punched potatoes, garlic, olive oil, and greens. Updated: 2024-07-20T09:00:00.000Z Source: src/content/posts/food/fish/06-polvo-a-lagareiro.md Polvo a Lagareiro is tender octopus with roasted potatoes and generous olive oil. ## Ingredients - Octopus - Small potatoes - Garlic - Olive oil - Greens for serving ## Method Simmer the octopus until tender, roast it with crushed potatoes and garlic, then finish with plenty of hot olive oil. ## Sardinhas Assadas URL: https://papyrus.marcelofelix.com/posts/sardinhas-assadas/ Summary: Grilled sardines served with roasted peppers, potatoes, bread, and olive oil. Updated: 2024-04-20T09:00:00.000Z Source: src/content/posts/food/fish/07-sardinhas-assadas.md Sardinhas Assadas are simple grilled sardines, especially popular in summer. ## Ingredients - Fresh sardines - Coarse salt - Roasted peppers - Boiled potatoes - Bread and olive oil ## Method Salt the sardines, grill over high heat, and serve immediately with potatoes, peppers, bread, and olive oil. ## Arroz de Marisco URL: https://papyrus.marcelofelix.com/posts/arroz-de-marisco/ Summary: Brothy seafood rice with prawns, clams, mussels, tomato, coriander, and stock. Updated: 2024-06-20T09:00:00.000Z Source: src/content/posts/food/fish/08-arroz-de-marisco.md Arroz de Marisco is saucy seafood rice, served loose rather than dry. ## Ingredients - Short-grain rice - Prawns, clams, and mussels - Tomato, onion, garlic, and pepper - Seafood stock - Coriander ## Method Cook the rice in seafood stock and tomato base, then add shellfish near the end so it stays tender. ## Caldeirada de Peixe URL: https://papyrus.marcelofelix.com/posts/caldeirada-de-peixe/ Summary: Layered fish stew with potatoes, peppers, tomatoes, onions, herbs, and olive oil. Updated: 2024-05-20T09:00:00.000Z Source: src/content/posts/food/fish/09-caldeirada-de-peixe.md Caldeirada de Peixe is a layered fisherman's stew built without much stirring. ## Ingredients - Mixed firm fish - Potatoes - Tomato, pepper, onion, and garlic - White wine - Parsley, coriander, and olive oil ## Method Layer vegetables and fish in a pot, season well, cover, and simmer gently until the potatoes and fish are cooked. ## Cozido a Portuguesa URL: https://papyrus.marcelofelix.com/posts/cozido-a-portuguesa/ Summary: A slow Portuguese meat and vegetable stew with cabbage, potatoes, sausages, and beef. Updated: 2024-01-10T09:00:00.000Z Source: src/content/posts/food/meat/10-cozido-a-portuguesa.md Cozido a Portuguesa is a generous one-pot meal built from meats, smoked sausages, cabbage, potatoes, carrots, and turnips. ## Ingredients - Beef shank or brisket - Pork ribs or belly - Chourico and farinheira - Cabbage, potatoes, carrots, and turnips - Salt, pepper, and bay leaf ## Method Simmer the meats first, then add the vegetables in stages so everything finishes tender. Serve the broth, meats, and vegetables together on a wide platter. ## Leitao a Bairrada URL: https://papyrus.marcelofelix.com/posts/leitao-a-bairrada/ Summary: Crisp roast suckling pig seasoned with garlic, pepper, lard, and salt. Updated: 2024-10-10T09:00:00.000Z Source: src/content/posts/food/meat/11-leitao-a-bairrada.md Leitao a Bairrada is famous for crisp skin, peppery seasoning, and juicy meat. ## Ingredients - Suckling pig - Garlic, coarse salt, pepper, and lard - Bay leaf - Orange slices for serving - Simple salad or chips ## Method Rub the pig with seasoning, roast until the skin is crisp, and serve in slices with a bright, simple garnish. ## Carne de Porco a Alentejana URL: https://papyrus.marcelofelix.com/posts/carne-de-porco-a-alentejana/ Summary: Marinated pork cubes cooked with clams, fried potatoes, coriander, and pickles. Updated: 2024-02-10T09:00:00.000Z Source: src/content/posts/food/meat/12-carne-de-porco-a-alentejana.md Carne de Porco a Alentejana pairs paprika-marinated pork with clams and crisp potatoes. ## Ingredients - Pork shoulder, cut into cubes - Garlic, paprika, bay leaf, and white wine - Clams - Potatoes for frying - Coriander and pickled vegetables ## Method Marinate the pork, brown it, then steam the clams in the pan juices. Fold in the fried potatoes and finish with coriander and pickles. ## Prego no Prato URL: https://papyrus.marcelofelix.com/posts/prego-no-prato/ Summary: Garlic steak served on a plate with fries, rice, egg, salad, and mustard. Updated: 2024-09-10T09:00:00.000Z Source: src/content/posts/food/meat/13-prego-no-prato.md Prego no Prato is the plated version of the Portuguese garlic steak sandwich. ## Ingredients - Thin beef steak - Garlic, butter, and bay leaf - Fried egg - Rice, fries, and salad - Mustard ## Method Sear the steak in garlic butter, fry the egg, and serve with the classic sides and a spoonful of sauce from the pan. ## Francesinha URL: https://papyrus.marcelofelix.com/posts/francesinha/ Summary: Porto's layered sandwich with steak, sausage, ham, melted cheese, and beer-tomato sauce. Updated: 2024-03-10T09:00:00.000Z Source: src/content/posts/food/meat/14-francesinha.md Francesinha is a rich Porto sandwich covered with melted cheese and a spicy beer-tomato sauce. ## Ingredients - Thick bread slices - Steak, fresh sausage, and ham - Sliced cheese - Tomato, beer, stock, and piri-piri for the sauce - Fries for serving ## Method Grill the meats, stack the sandwich, cover it with cheese, and bake until melted. Pour hot sauce over the sandwich just before serving. ## Feijoada a Transmontana URL: https://papyrus.marcelofelix.com/posts/feijoada-a-transmontana/ Summary: Northern bean stew with pork, smoked sausages, cabbage, and rice. Updated: 2024-08-10T09:00:00.000Z Source: src/content/posts/food/meat/15-feijoada-a-transmontana.md Feijoada a Transmontana is a rich bean stew from the north, usually served with rice. ## Ingredients - Red or white beans - Pork ribs, ear, or belly - Chourico and morcela - Cabbage - Onion, garlic, paprika, and bay leaf ## Method Cook the beans and meats until soft, add cabbage near the end, and serve with plain rice. ## Bitoque URL: https://papyrus.marcelofelix.com/posts/bitoque/ Summary: A simple steak plate with fried egg, rice, fries, and a quick garlic pan sauce. Updated: 2024-04-10T09:00:00.000Z Source: src/content/posts/food/meat/16-bitoque.md Bitoque is a weekday classic: thin steak, fried egg, rice, fries, and a savory pan sauce. ## Ingredients - Thin beef steaks - Garlic, bay leaf, and butter - Eggs - Rice and fries - Mustard or white wine for the sauce ## Method Sear the steaks quickly, loosen the pan with wine or mustard, and serve with the fried egg on top. ## Tripas a Moda do Porto URL: https://papyrus.marcelofelix.com/posts/tripas-a-moda-do-porto/ Summary: Porto-style tripe stew with beans, smoked meats, carrot, and cumin. Updated: 2024-07-10T09:00:00.000Z Source: src/content/posts/food/meat/17-tripas-a-moda-do-porto.md Tripas a Moda do Porto is a hearty stew of tripe, beans, sausage, and aromatic spices. ## Ingredients - Cleaned tripe - White beans - Chourico and smoked meats - Onion, carrot, garlic, and bay leaf - Cumin and pepper ## Method Cook the tripe until tender, then stew it with beans and smoked meats until the sauce is thick and savory. ## Arroz de Pato URL: https://papyrus.marcelofelix.com/posts/arroz-de-pato/ Summary: Duck rice baked with broth, chourico, and a crisp golden top. Updated: 2024-05-10T09:00:00.000Z Source: src/content/posts/food/meat/18-arroz-de-pato.md Arroz de Pato turns tender duck and its broth into a baked rice dish topped with chourico. ## Ingredients - Duck legs - Onion, garlic, bay leaf, and carrot - Rice - Chourico slices - Parsley ## Method Poach the duck, shred the meat, cook rice in the broth, then bake with chourico until the top is lightly crisp. ## Cabrito Assado URL: https://papyrus.marcelofelix.com/posts/cabrito-assado/ Summary: Roast kid goat with garlic, white wine, bay leaf, potatoes, and paprika. Updated: 2024-06-10T09:00:00.000Z Source: src/content/posts/food/meat/19-cabrito-assado.md Cabrito Assado is a celebratory roast, often served with potatoes and greens. ## Ingredients - Kid goat pieces - Garlic, bay leaf, paprika, and white wine - Olive oil - Potatoes - Parsley and lemon ## Method Marinate overnight, roast slowly with potatoes, and baste until the meat is tender and the potatoes are browned.