diff --git a/docs/01-getting-started/index.md b/docs/01-getting-started/index.md
new file mode 100644
index 0000000..cf604db
--- /dev/null
+++ b/docs/01-getting-started/index.md
@@ -0,0 +1,165 @@
+# Getting Started with FolderWeb
+
+Welcome to FolderWeb—a delightfully minimal PHP framework that turns your folder structure into a website. No build steps, no package managers, no JavaScript frameworks. Just files and folders doing what they do best.
+
+## What You Need
+
+- **PHP 8.4+** — Modern PHP with all the good stuff
+- **A web server** — Apache, Nginx, or just PHP's built-in server for local development
+- **A text editor** — Whatever makes you happy
+
+That's it. No npm, no webpack, no node_modules folder the size of the observable universe.
+
+## Quick Start (5 Minutes)
+
+### 1. Get the Code
+
+```bash
+git clone https://github.com/yourusername/folderweb.git
+cd folderweb
+```
+
+### 2. Set Up Your Custom Directory
+
+FolderWeb separates framework code (`app/`) from your customizations (`custom/`). Copy the defaults to get started:
+
+**Unix/Linux/macOS:**
+```bash
+cp -r app/default custom
+```
+
+**Windows (PowerShell):**
+```powershell
+Copy-Item -Recurse app\default custom
+```
+
+**Or just:** Copy the `app/default` folder and rename it to `custom` using your file manager.
+
+### 3. Create Your Content Directory
+
+```bash
+mkdir content
+```
+
+This is where your actual website content lives—separate from the framework.
+
+### 4. Fire It Up Locally
+
+**Using Podman (Recommended):**
+
+If you have [Podman](https://podman.io/) installed, there's a ready-made setup:
+
+```bash
+cd devel
+podman-compose up
+```
+
+Visit `http://localhost:8080` and you're live.
+
+**Using PHP's Built-in Server:**
+
+```bash
+php -S localhost:8080 -t .
+```
+
+Simple, but you'll need to configure routing manually for production.
+
+### 5. Make Your First Page
+
+Create a file at `content/hello.md`:
+
+```markdown
+# Hello, World!
+
+This is my first page. Look ma, no build step!
+```
+
+Visit `http://localhost:8080/hello/` and there it is.
+
+**Pro tip:** Notice the trailing slash? FolderWeb enforces them. Folders are folders, after all.
+
+## What Just Happened?
+
+FolderWeb looked at your request (`/hello/`), found `content/hello.md`, processed the Markdown into HTML, wrapped it in a template, and served it. All in milliseconds.
+
+The magic is simple:
+- **Folders = URLs:** Your directory structure is your site structure
+- **Files = Content:** Drop a `.md`, `.html`, or `.php` file and it renders
+- **Templates = Presentation:** HTML wrappers that make everything pretty
+- **Metadata = Configuration:** Optional `.ini` files for titles, dates, and settings
+
+## Next Steps
+
+Now that you're up and running, you can:
+
+1. **[Add more content](#)** — Learn about Markdown files, metadata, and organizing pages
+2. **[Customize the design](#)** — Edit templates and CSS to make it yours
+3. **[Deploy to production](#)** — Get your site online with a web host
+
+Or just keep making pages. It's your website—do what makes you happy.
+
+---
+
+## Deploying to Production
+
+When you're ready to go live, you'll need a web host with PHP support. Look for:
+
+- **PHP 8.4+** (or at least 8.0, but why settle?)
+- **Apache or Nginx** with mod_rewrite or equivalent
+- **SSH access** (optional, but makes life easier)
+
+Most shared hosting providers offer this. Avoid anything that says "managed WordPress only"—you're too cool for that.
+
+### Deployment with Symlinks (Recommended)
+
+Keep the framework separate from the web root for easy upgrades:
+
+```bash
+# Your server structure:
+/home/yourusername/
+├── folderweb/ # Git repo (not public)
+│ ├── app/
+│ └── custom/
+├── content/ # Your content (not public)
+└── public_html/ # Web root (public)
+ ├── app -> ../folderweb/app/ # Symlink
+ ├── custom -> ../folderweb/custom/ # Symlink
+ └── content -> ../content/ # Symlink
+```
+
+**Why?** When you update FolderWeb, just `git pull` and you're done. No copying files, no risk of overwriting customizations.
+
+### Apache Configuration
+
+If your host lets you use `.htaccess`, FolderWeb will handle routing automatically. Otherwise, add this to your Apache config:
+
+```apache
+
+ RewriteEngine On
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteRule ^(.*)$ /app/router.php [L,QSA]
+
+```
+
+### Nginx Configuration
+
+```nginx
+location / {
+ try_files $uri $uri/ /app/router.php?$query_string;
+}
+```
+
+That's it. Upload your files, point your domain, and you're live.
+
+---
+
+## What's Next?
+
+Ready to dive deeper? Head to the [Tutorial](#) to learn how to:
+- Organize content with folders and metadata
+- Customize templates and styles
+- Add multilingual support
+- Create list views and navigation menus
+
+Or jump straight to the [Reference](#) if you're the "read the manual" type.
diff --git a/docs/02-tutorial/01-adding-content.md b/docs/02-tutorial/01-adding-content.md
new file mode 100644
index 0000000..05a2fde
--- /dev/null
+++ b/docs/02-tutorial/01-adding-content.md
@@ -0,0 +1,308 @@
+# Adding Content
+
+FolderWeb turns your folder structure into a website. That's not marketing speak—it's literally how it works. Let's explore what that means in practice.
+
+## The Basic Idea
+
+```
+content/
+├── about.md → yoursite.com/about/
+├── blog/
+│ └── index.md → yoursite.com/blog/
+└── contact.html → yoursite.com/contact/
+```
+
+Every file becomes a page. Every folder becomes a URL path. No configuration required.
+
+## File Types
+
+FolderWeb recognizes three content types:
+
+### Markdown (`.md`)
+
+The bread and butter of content creation. Write in Markdown, get HTML.
+
+```markdown
+# My Page Title
+
+This is a paragraph with **bold** and *italic* text.
+
+- Lists work
+- And so do [links](https://example.com)
+
+## Subheading
+
+More content here.
+```
+
+FolderWeb uses [Parsedown](https://parsedown.org/) to convert Markdown to HTML, with caching for performance.
+
+### HTML (`.html`)
+
+When you need more control or already have HTML:
+
+```html
+
My Page
+This is plain HTML.
+
+ Whatever you want.
+
+```
+
+### PHP (`.php`)
+
+For dynamic content:
+
+```php
+Copyright {$currentYear}";
+?>
+
+Latest Posts
+{$post}";
+}
+?>
+```
+
+**Important:** PHP files are executed, so be careful with user input and security.
+
+## Folder Structure as URL Structure
+
+Your folder hierarchy determines your URLs:
+
+```
+content/
+├── index.md → /
+├── about.md → /about/
+├── blog/
+│ ├── index.md → /blog/
+│ ├── 2024-12-15-first-post/
+│ │ └── index.md → /blog/first-post/
+│ └── 2024-12-20-second-post/
+│ └── index.md → /blog/second-post/
+└── projects/
+ ├── index.md → /projects/
+ └── my-project/
+ └── index.md → /projects/my-project/
+```
+
+**Key points:**
+- Folder names become URL slugs
+- Date prefixes (`YYYY-MM-DD-`) are stripped from URLs but preserved for sorting
+- Trailing slashes are enforced (FolderWeb redirects `/about` → `/about/`)
+
+## Multiple Files in One Page
+
+You can combine multiple content files in a single directory. They render in **alphabetical order**:
+
+```
+content/portfolio/
+├── 00-hero.php # Renders first
+├── 01-intro.md # Renders second
+├── 02-gallery.html # Renders third
+└── 03-contact.md # Renders last
+```
+
+All four files render as one page at `/portfolio/`.
+
+**Use case:** Build pages from modular components—header, content, footer, etc.
+
+## Dates in Folder Names
+
+FolderWeb automatically extracts dates from folder names:
+
+```
+content/blog/
+├── 2024-12-15-my-first-post/ # Date: December 15, 2024
+├── 2024-12-20-another-post/ # Date: December 20, 2024
+└── 2025-01-01-new-year-post/ # Date: January 1, 2025
+```
+
+The date format is `YYYY-MM-DD-` and it's **stripped from the URL**:
+
+- Folder: `2024-12-15-my-first-post`
+- URL: `/blog/my-first-post/`
+- Date: Extracted and available in templates
+
+Dates are automatically formatted based on your language (e.g., "15. desember 2024" in Norwegian).
+
+## Adding Assets (Images, Files)
+
+Drop assets directly in your content folders:
+
+```
+content/blog/my-post/
+├── index.md
+├── cover.jpg # Cover image (automatic)
+├── diagram.png # Referenced in content
+└── styles.css # Page-specific styles
+```
+
+**Reference in Markdown:**
+
+```markdown
+
+```
+
+**Cover images:** Name your cover image `cover.jpg`, `cover.png`, or `cover.webp`. FolderWeb automatically uses it in list views and social media meta tags.
+
+## Metadata Files
+
+Add a `metadata.ini` file to configure pages:
+
+```
+content/blog/my-post/
+├── index.md
+└── metadata.ini
+```
+
+**Basic metadata:**
+
+```ini
+title = "My Awesome Post"
+summary = "A short description for list views"
+date = "2024-12-15"
+```
+
+**More options:**
+
+```ini
+title = "My Post"
+summary = "Short description"
+date = "2024-12-15"
+search_description = "SEO-friendly description for search engines"
+menu = 1 # Show in navigation menu
+menu_order = 10 # Menu position (lower = first)
+
+[settings]
+show_date = true # Display date on page
+hide_list = false # Don't show list view even if subdirectories exist
+```
+
+See the [Metadata Reference](#) for all available options.
+
+## List Views vs. Page Views
+
+FolderWeb automatically decides whether to show a **list** or a **page**:
+
+**List view** (directory has subdirectories):
+```
+content/blog/
+├── index.md # Intro content
+├── metadata.ini # List configuration
+├── 2024-12-15-first-post/
+└── 2024-12-20-second-post/
+```
+
+Result: `/blog/` shows a list of posts with the intro content at the top.
+
+**Page view** (directory has only files):
+```
+content/about/
+└── index.md
+```
+
+Result: `/about/` shows just the page content.
+
+**Override:** Use `hide_list = true` in `metadata.ini` to force page view even with subdirectories.
+
+## Custom URL Slugs
+
+Don't like your folder name as the URL? Override it with metadata:
+
+```ini
+slug = "custom-url-path"
+```
+
+**Example:**
+
+```
+content/blog/2024-12-15-very-long-title-that-i-regret/
+└── metadata.ini
+```
+
+```ini
+slug = "short-title"
+```
+
+URL becomes `/blog/short-title/` instead of `/blog/very-long-title-that-i-regret/`.
+
+## Navigation Menus
+
+Add pages to the navigation menu with metadata:
+
+```ini
+title = "About Us"
+menu = 1 # Show in menu
+menu_order = 20 # Position (lower numbers first)
+```
+
+Menu items are sorted by `menu_order`, then alphabetically by title.
+
+## Practical Examples
+
+### Simple Blog Post
+
+```
+content/blog/2024-12-15-my-first-post/
+├── index.md
+├── cover.jpg
+└── metadata.ini
+```
+
+**index.md:**
+```markdown
+# My First Post
+
+This is my blog post content.
+
+
+```
+
+**metadata.ini:**
+```ini
+title = "My First Post"
+summary = "An introduction to my blog"
+```
+
+### Multi-Section Page
+
+```
+content/services/
+├── 00-hero.php
+├── 01-intro.md
+├── 02-pricing.html
+└── metadata.ini
+```
+
+All files render together as one page at `/services/`.
+
+### Documentation Site
+
+```
+content/docs/
+├── index.md
+├── getting-started/
+│ └── index.md
+├── tutorial/
+│ ├── index.md
+│ ├── basics.md
+│ └── advanced.md
+└── reference/
+ └── index.md
+```
+
+Creates a hierarchical documentation structure with automatic list views.
+
+## What's Next?
+
+Now that you know how to add content, learn how to:
+- **[Customize styling](#)** — Make it look like your own
+- **[Create templates](#)** — Control how content is presented
+- **[Add multilingual support](#)** — Reach a global audience
+
+Or jump to the [Reference](#) for detailed documentation on metadata, templates, and configuration.
diff --git a/docs/02-tutorial/02-styling.md b/docs/02-tutorial/02-styling.md
new file mode 100644
index 0000000..0aa78ba
--- /dev/null
+++ b/docs/02-tutorial/02-styling.md
@@ -0,0 +1,432 @@
+# Styling Your Site
+
+FolderWeb embraces modern CSS—no preprocessors, no build steps, just the good stuff browsers support today. Let's make your site look exactly how you want.
+
+## Where Styles Live
+
+FolderWeb has a simple style hierarchy:
+
+```
+custom/
+├── styles/
+│ ├── base.css # Your main stylesheet
+│ └── custom-theme.css # Additional stylesheets (optional)
+└── content/
+ └── my-page/
+ └── styles.css # Page-specific styles (optional)
+```
+
+**Loading order:**
+1. `custom/styles/base.css` — Your main styles (always loaded)
+2. Page-specific `styles.css` — If it exists in the content directory
+
+## Editing the Main Stylesheet
+
+Start by editing `custom/styles/base.css`. This is where your site-wide styles live.
+
+**Default structure:**
+
+```css
+/* CSS Variables (Design Tokens) */
+:root {
+ --color-primary: oklch(60% 0.15 250);
+ --color-text: oklch(20% 0 0);
+ --color-bg: oklch(98% 0 0);
+ --font-base: system-ui, -apple-system, sans-serif;
+ --font-mono: 'SF Mono', Monaco, monospace;
+ --spacing-unit: 1rem;
+}
+
+/* Base Typography */
+body {
+ font-family: var(--font-base);
+ color: var(--color-text);
+ background: var(--color-bg);
+ line-height: 1.6;
+}
+
+/* More styles... */
+```
+
+Edit these values to match your brand. No build step, no compilation—just save and refresh.
+
+## Modern CSS Features
+
+FolderWeb encourages using modern CSS features. Here's what you should know:
+
+### CSS Variables (Custom Properties)
+
+Define once, use everywhere:
+
+```css
+:root {
+ --color-accent: oklch(65% 0.2 150);
+ --radius-sm: 0.25rem;
+ --shadow: 0 2px 8px oklch(0% 0 0 / 0.1);
+}
+
+.button {
+ background: var(--color-accent);
+ border-radius: var(--radius-sm);
+ box-shadow: var(--shadow);
+}
+```
+
+### OKLCH Colors
+
+Use modern color spaces for better color manipulation:
+
+```css
+/* Traditional RGB/HSL */
+--old: rgb(100, 150, 200);
+--old-hsl: hsl(210, 50%, 60%);
+
+/* Modern OKLCH (better perceptual uniformity) */
+--new: oklch(65% 0.1 250);
+```
+
+**Format:** `oklch(lightness chroma hue / alpha)`
+- **Lightness:** 0% (black) to 100% (white)
+- **Chroma:** 0 (gray) to ~0.4 (vivid)
+- **Hue:** 0-360 degrees (color wheel)
+
+### CSS Nesting
+
+Nest related styles without preprocessors:
+
+```css
+.card {
+ padding: 1rem;
+ border-radius: 0.5rem;
+
+ & h2 {
+ margin-top: 0;
+ }
+
+ & p {
+ color: var(--color-text-muted);
+ }
+
+ &:hover {
+ box-shadow: var(--shadow-lg);
+ }
+}
+```
+
+### Fluid Typography with `clamp()`
+
+Responsive sizing without media queries:
+
+```css
+h1 {
+ /* Min 2rem, ideal 5vw, max 4rem */
+ font-size: clamp(2rem, 5vw, 4rem);
+}
+
+p {
+ /* Min 1rem, ideal 1.125rem, max 1.25rem */
+ font-size: clamp(1rem, 1.125rem, 1.25rem);
+}
+```
+
+### Logical Properties
+
+Use logical properties for better internationalization:
+
+```css
+/* Old way (assumes left-to-right) */
+.old {
+ margin-left: 1rem;
+ padding-right: 2rem;
+}
+
+/* New way (respects text direction) */
+.new {
+ margin-inline-start: 1rem;
+ padding-inline-end: 2rem;
+}
+```
+
+**Common logical properties:**
+- `margin-inline-start` / `margin-inline-end` (left/right in LTR)
+- `margin-block-start` / `margin-block-end` (top/bottom)
+- `padding-inline` / `padding-block`
+- `inline-size` (width)
+- `block-size` (height)
+
+### Grid Layout
+
+Use CSS Grid for layout (not flexbox for everything):
+
+```css
+.page-layout {
+ display: grid;
+ grid-template-columns: 1fr min(65ch, 100%) 1fr;
+ gap: 2rem;
+
+ & > * {
+ grid-column: 2;
+ }
+
+ & > .full-width {
+ grid-column: 1 / -1;
+ }
+}
+```
+
+## Classless CSS Philosophy
+
+FolderWeb defaults to **classless CSS**—styling HTML elements directly instead of adding classes everywhere.
+
+**Good (classless):**
+```html
+
+ Page Title
+ Content here.
+
+```
+
+```css
+article {
+ max-width: 65ch;
+ margin: 0 auto;
+
+ & h1 {
+ font-size: 2.5rem;
+ margin-bottom: 1rem;
+ }
+
+ & p {
+ line-height: 1.7;
+ }
+}
+```
+
+**Less good (class-heavy):**
+```html
+
+ Page Title
+ Content here.
+
+```
+
+**When to use classes:**
+- Component variants (`.button-primary`, `.button-secondary`)
+- JavaScript hooks (`.js-toggle`)
+- Utility overrides (`.visually-hidden`)
+
+## Page-Specific Styles
+
+Add `styles.css` to a content directory for page-specific styling:
+
+```
+content/portfolio/
+├── index.md
+└── styles.css
+```
+
+**styles.css:**
+```css
+/* Scoped to this page only */
+.portfolio-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 2rem;
+}
+```
+
+FolderWeb automatically loads and includes page-specific styles with cache-busting:
+
+```html
+
+```
+
+## Dark Mode
+
+Add dark mode with CSS variables and `prefers-color-scheme`:
+
+```css
+:root {
+ --color-bg: oklch(98% 0 0);
+ --color-text: oklch(20% 0 0);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-bg: oklch(15% 0 0);
+ --color-text: oklch(95% 0 0);
+ }
+}
+```
+
+All colors using the variables automatically adapt.
+
+## Responsive Design
+
+Use fluid layouts and relative units:
+
+```css
+/* Bad: fixed breakpoints */
+@media (min-width: 768px) {
+ .container {
+ width: 750px;
+ }
+}
+
+/* Good: fluid and flexible */
+.container {
+ width: min(90%, 1200px);
+ padding: clamp(1rem, 3vw, 3rem);
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
+ gap: clamp(1rem, 3vw, 2rem);
+}
+```
+
+**Use media queries sparingly:**
+- Layout changes (sidebar position)
+- Font size adjustments
+- Complex interactions
+
+**Prefer fluid CSS:**
+- Spacing (`clamp()`)
+- Typography (`clamp()`)
+- Grids (`auto-fit`, `minmax()`)
+
+## Cache Busting
+
+FolderWeb automatically versions CSS files with MD5 hashes:
+
+```html
+
+```
+
+When you edit your CSS, the hash changes and browsers fetch the new version. No manual cache clearing needed.
+
+## Practical Examples
+
+### Simple Blog Theme
+
+```css
+:root {
+ --color-accent: oklch(55% 0.15 220);
+ --color-text: oklch(25% 0 0);
+ --color-bg: oklch(99% 0 0);
+ --max-width: 65ch;
+}
+
+body {
+ font-family: Georgia, serif;
+ color: var(--color-text);
+ background: var(--color-bg);
+ padding: 2rem 1rem;
+}
+
+article {
+ max-width: var(--max-width);
+ margin: 0 auto;
+
+ & h1 {
+ font-size: clamp(2rem, 5vw, 3rem);
+ color: var(--color-accent);
+ }
+
+ & p {
+ line-height: 1.8;
+ margin-bottom: 1.5rem;
+ }
+}
+```
+
+### Portfolio Grid
+
+```css
+.project-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
+ gap: 2rem;
+ padding: 2rem 0;
+}
+
+.project-card {
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: 0 2px 8px oklch(0% 0 0 / 0.1);
+ transition: transform 0.2s;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 4px 16px oklch(0% 0 0 / 0.15);
+ }
+
+ & img {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ object-fit: cover;
+ }
+
+ & h3 {
+ padding: 1rem;
+ margin: 0;
+ }
+}
+```
+
+### Documentation Site
+
+```css
+:root {
+ --sidebar-width: 250px;
+}
+
+.docs-layout {
+ display: grid;
+ grid-template-columns: var(--sidebar-width) 1fr;
+ gap: 3rem;
+ max-width: 1400px;
+ margin: 0 auto;
+
+ @media (max-width: 900px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.sidebar nav {
+ position: sticky;
+ top: 2rem;
+
+ & ul {
+ list-style: none;
+ padding: 0;
+ }
+
+ & a {
+ display: block;
+ padding: 0.5rem 1rem;
+ text-decoration: none;
+ border-radius: 0.25rem;
+
+ &:hover {
+ background: var(--color-bg-hover);
+ }
+ }
+}
+```
+
+## Tips
+
+- **Start simple:** Edit colors and fonts first, then tackle layout
+- **Use the inspector:** Browser dev tools show computed values and help debug
+- **Test in different browsers:** Modern CSS has excellent support, but always verify
+- **Keep it readable:** Future you will thank present you for clear, organized styles
+
+## What's Next?
+
+Now that you can style your site, learn how to:
+- **[Create custom templates](#)** — Control HTML structure and layout
+- **[Add multilingual support](#)** — Style for different languages and text directions
+
+Or explore the [Reference](#) for detailed documentation on all available template variables and hooks.
diff --git a/docs/02-tutorial/03-templates.md b/docs/02-tutorial/03-templates.md
new file mode 100644
index 0000000..5e14654
--- /dev/null
+++ b/docs/02-tutorial/03-templates.md
@@ -0,0 +1,461 @@
+# Working with Templates
+
+Templates control how your content is presented. FolderWeb uses a simple PHP-based template system—no complex templating languages, just HTML with a sprinkle of PHP.
+
+## Template Types
+
+FolderWeb has three template levels:
+
+### 1. Base Template (`base.php`)
+
+The HTML scaffold wrapping every page:
+
+```
+
+
+
+ Page Title
+
+
+
+
+
+
+
+
+```
+
+**You typically customize this once** to set up your site structure.
+
+### 2. Page Template (`page.php`)
+
+Wraps single-page content:
+
+```php
+
+ = $content ?>
+
+```
+
+**Customize this** to control how individual pages look.
+
+### 3. List Template (`list.php`, `list-grid.php`, `list-compact.php`)
+
+Displays multiple items from subdirectories:
+
+```php
+= $pageContent ?>
+
+
+
+
+
+ = $item['summary'] ?>
+
+
+
+```
+
+**Customize this** to control how lists of content (blogs, portfolios, etc.) appear.
+
+## Template Location
+
+Templates live in your `custom/` directory:
+
+```
+custom/
+└── templates/
+ ├── base.php # HTML scaffold
+ ├── page.php # Single page wrapper
+ ├── list.php # Default list layout
+ ├── list-grid.php # Grid card layout
+ └── list-compact.php # Compact list layout
+```
+
+**FolderWeb falls back** to `app/default/templates/` if a custom template doesn't exist.
+
+## Customizing the Base Template
+
+Let's modify `base.php` to add your site name and custom navigation:
+
+**custom/templates/base.php:**
+
+```php
+
+
+
+
+
+ = htmlspecialchars($pageTitle) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = $content ?>
+
+
+
+
+
+```
+
+**Key points:**
+- Always escape user content: `htmlspecialchars($var)`
+- Use short echo tags: `= $var ?>`
+- Check if variables exist: `isset($var)`
+- The `$content` variable contains the rendered page/list content
+
+## Customizing Page Templates
+
+The page template wraps your single-page content. Let's add a reading time estimate:
+
+**custom/templates/page.php:**
+
+```php
+
+
+
+
+
+
+ = $content ?>
+
+
+```
+
+## Customizing List Templates
+
+List templates display collections of content. Let's create a custom blog list:
+
+**custom/templates/list.php:**
+
+```php
+
+
+ = $pageContent ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = $item['formatted_date'] ?? $item['date'] ?>
+
+
+
+
+
+ = htmlspecialchars($item['summary']) ?>
+
+
+ Read more →
+
+
+
+```
+
+## Choosing List Templates
+
+You can create multiple list templates and select them per directory:
+
+**Available by default:**
+- `list.php` — Simple vertical list
+- `list-grid.php` — Card grid layout
+- `list-compact.php` — Minimal compact list
+
+**Select in metadata.ini:**
+
+```ini
+title = "Projects"
+
+[settings]
+page_template = "list-grid"
+```
+
+Now the `projects/` directory uses the grid layout.
+
+## Creating Custom List Templates
+
+Let's create a timeline template for a blog:
+
+**custom/templates/list-timeline.php:**
+
+```php
+= $pageContent ?>
+
+
+
+
+
= $year ?>
+
+
+
+
+ = $item['formatted_date'] ?? '' ?>
+
+
+
+
= htmlspecialchars($item['summary']) ?>
+
+
+
+
+
+```
+
+**Use in metadata.ini:**
+
+```ini
+[settings]
+page_template = "list-timeline"
+```
+
+## Available Template Variables
+
+Templates have access to these variables (see [Reference: Template Variables](#) for complete list):
+
+**Base template:**
+```php
+$content // Rendered page/list HTML
+$pageTitle // Page title for tag
+$metaDescription // SEO description
+$navigation // Array of menu items
+$homeLabel // "Home" link text (translated)
+$currentLang // Current language code
+$languageUrls // Links to other language versions
+$translations // Translated UI strings
+$cssHash // Cache-busting hash for CSS
+$pageCssUrl // Page-specific CSS URL (if exists)
+$pageLoadTime // Page generation time
+```
+
+**Page template:**
+```php
+$content // Rendered HTML
+$metadata // Metadata array (title, date, etc.)
+```
+
+**List template:**
+```php
+$items // Array of items to display
+$pageContent // Optional intro content from page
+$metadata // Directory metadata
+
+// Each $item has:
+$item['url'] // Full URL to item
+$item['title'] // Item title
+$item['summary'] // Short description
+$item['date'] // ISO date (YYYY-MM-DD)
+$item['formatted_date'] // Localized date string
+$item['cover_image'] // Cover image URL (if exists)
+```
+
+## Best Practices
+
+### 1. Always Escape Output
+
+```php
+
+= $title ?>
+
+
+= htmlspecialchars($title) ?>
+```
+
+**Exception:** Already-sanitized HTML like `$content` (rendered from Markdown).
+
+### 2. Check Variables Exist
+
+```php
+
+= $metadata['summary'] ?>
+
+
+
+ = htmlspecialchars($metadata['summary']) ?>
+
+```
+
+### 3. Use Short Echo Tags
+
+```php
+
+
+
+
+= htmlspecialchars($title) ?>
+```
+
+### 4. Keep Logic Minimal
+
+Templates should display data, not process it. Complex logic belongs in plugins.
+
+```php
+
+ strtotime('-30 days');
+});
+usort($posts, function($a, $b) {
+ return strcmp($b['date'], $a['date']);
+});
+?>
+
+
+
+ ...
+
+```
+
+### 5. Use Semantic HTML
+
+```php
+
+Title
+Content
+
+
+
+ Title
+ Content
+
+```
+
+## Practical Examples
+
+### Simple Portfolio Page Template
+
+```php
+
+
+
+
+
+
+ = htmlspecialchars($metadata['title'] ?? 'Untitled') ?>
+
+
+
+ = $content ?>
+
+
+
+
+
+
+```
+
+### Card Grid List Template
+
+```php
+= $pageContent ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
= htmlspecialchars($item['summary']) ?>
+
+
+
+
+
+```
+
+## What's Next?
+
+You now know how to customize templates. Next, learn about:
+- **[Template Variables Reference](#)** — Complete list of available variables
+- **[Creating Plugins](#)** — Extend functionality and add custom data to templates
+- **[Internationalization](#)** — Build multilingual sites
+
+Or explore the examples in `app/default/content/examples/templates-demo/` to see templates in action.
diff --git a/docs/03-reference/01-configuration.md b/docs/03-reference/01-configuration.md
new file mode 100644
index 0000000..7c3a7fc
--- /dev/null
+++ b/docs/03-reference/01-configuration.md
@@ -0,0 +1,304 @@
+# Configuration Reference
+
+FolderWeb uses INI files for configuration. The configuration system follows a simple hierarchy with sensible defaults.
+
+## Configuration Files
+
+```
+app/default/config.ini # Framework defaults (don't modify)
+custom/config.ini # Your overrides (create this)
+```
+
+**How it works:**
+1. FolderWeb loads `app/default/config.ini`
+2. If `custom/config.ini` exists, its values override the defaults
+3. Only override what you need—missing values fall back to defaults
+
+## Creating Your Configuration
+
+**custom/config.ini:**
+
+```ini
+[languages]
+default = "en"
+available = "en,no,de"
+
+[plugins]
+enabled = "languages,my-custom-plugin"
+
+[site]
+title = "My Website"
+```
+
+That's it. Only add what you need to change.
+
+## Available Configuration Options
+
+### `[languages]`
+
+Controls multilingual support (requires the `languages` plugin).
+
+```ini
+[languages]
+default = "en" # Default language code
+available = "en,no,de" # Comma-separated list of available languages
+```
+
+**Values:**
+- `default` — Language code used when no language is specified in URL
+- `available` — Comma-separated list of language codes (ISO 639-1)
+
+**Example:**
+```ini
+[languages]
+default = "no"
+available = "no,en"
+```
+
+### `[plugins]`
+
+Controls which plugins are loaded.
+
+```ini
+[plugins]
+enabled = "languages,analytics,custom-plugin"
+```
+
+**Values:**
+- `enabled` — Comma-separated list of plugin names (without `.php` extension)
+
+**Plugin loading order:**
+1. `app/plugins/global/` — Built-in global plugins
+2. `custom/plugins/global/` — Your global plugins
+3. `app/plugins/page/` — Built-in page plugins (not yet used)
+4. `custom/plugins/page/` — Your page plugins (not yet used)
+
+**Example:**
+```ini
+[plugins]
+enabled = "languages"
+```
+
+To disable all plugins, leave the value empty:
+```ini
+[plugins]
+enabled = ""
+```
+
+### Custom Sections
+
+Add your own configuration sections for custom plugins:
+
+```ini
+[analytics]
+tracking_id = "UA-12345678-1"
+enabled = true
+
+[social]
+twitter = "@myhandle"
+github = "myusername"
+
+[api]
+endpoint = "https://api.example.com"
+key = "secret-key-here"
+```
+
+Access in plugins via the `$config` parameter:
+
+```php
+Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
+ $trackingId = $config['analytics']['tracking_id'] ?? null;
+ // Use the config value...
+ return $ctx;
+});
+```
+
+## Default Configuration
+
+Here's what's included in `app/default/config.ini`:
+
+```ini
+[languages]
+default = "en"
+available = "en,no"
+
+[plugins]
+enabled = "languages"
+```
+
+These values are active unless you override them in `custom/config.ini`.
+
+## Configuration Best Practices
+
+### 1. Only Override What Changes
+
+**Bad:**
+```ini
+[languages]
+default = "en"
+available = "en,no"
+
+[plugins]
+enabled = "languages"
+```
+
+**Good:**
+```ini
+# Only change the default language
+[languages]
+default = "no"
+```
+
+### 2. Use Comments
+
+```ini
+[languages]
+default = "no" # Norwegian site
+available = "no,en,de" # Also support English and German
+
+[plugins]
+enabled = "languages,analytics" # Google Analytics plugin
+```
+
+### 3. Keep Secrets Separate
+
+Don't commit API keys and secrets to version control. Use environment-specific config or `.gitignore`:
+
+```ini
+[api]
+key = "dev-key-here" # Override in production
+```
+
+### 4. Organize by Purpose
+
+```ini
+# Multilingual settings
+[languages]
+default = "en"
+available = "en,no"
+
+# Third-party services
+[analytics]
+enabled = true
+tracking_id = "UA-12345678-1"
+
+# Custom features
+[reading_time]
+words_per_minute = 200
+```
+
+## Environment-Specific Configuration
+
+FolderWeb doesn't have built-in environment detection, but you can handle it manually:
+
+**Option 1: Different files**
+
+```bash
+# Development
+ln -s custom/config.dev.ini custom/config.ini
+
+# Production
+ln -s custom/config.prod.ini custom/config.ini
+```
+
+**Option 2: Server-side includes**
+
+**custom/config.ini:**
+```ini
+[languages]
+default = "en"
+```
+
+**custom/config.prod.ini:**
+```ini
+[api]
+key = "production-key"
+```
+
+Load production config in your deployment script:
+```bash
+cat custom/config.prod.ini >> custom/config.ini
+```
+
+**Option 3: Environment variables**
+
+Read from environment variables in a custom plugin:
+
+```php
+Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
+ // Override config with environment variables
+ $apiKey = getenv('API_KEY') ?: ($config['api']['key'] ?? null);
+ $ctx->set('api_key', $apiKey);
+ return $ctx;
+});
+```
+
+## Accessing Configuration in Code
+
+Configuration is passed to plugin hooks:
+
+```php
+Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
+ // Access configuration
+ $defaultLang = $config['languages']['default'] ?? 'en';
+ $plugins = $config['plugins']['enabled'] ?? '';
+
+ // Use it
+ $ctx->set('site_lang', $defaultLang);
+
+ return $ctx;
+});
+```
+
+Configuration is **not** directly available in templates. If you need config values in templates, set them via a plugin hook:
+
+```php
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ global $config;
+
+ $vars['siteTitle'] = $config['site']['title'] ?? 'My Site';
+ $vars['socialLinks'] = [
+ 'twitter' => $config['social']['twitter'] ?? null,
+ 'github' => $config['social']['github'] ?? null,
+ ];
+
+ return $vars;
+});
+```
+
+## Configuration Schema
+
+FolderWeb doesn't enforce a schema—you can add any sections and keys you need. However, these are the recognized built-in options:
+
+| Section | Key | Type | Default | Description |
+|---------|-----|------|---------|-------------|
+| `languages` | `default` | string | `"en"` | Default language code |
+| `languages` | `available` | string | `"en,no"` | Comma-separated language codes |
+| `plugins` | `enabled` | string | `"languages"` | Comma-separated plugin names |
+
+All other sections are custom and plugin-specific.
+
+## Debugging Configuration
+
+To see the active configuration, create a debug page:
+
+**content/debug.php:**
+
+```php
+';
+print_r($config);
+echo '';
+?>
+```
+
+Visit `/debug/` to see the merged configuration array.
+
+**Remember to delete this page** before deploying to production.
+
+## What's Next?
+
+- **[Metadata Reference](#)** — Configure individual pages with `metadata.ini`
+- **[Template Variables](#)** — Access configuration in templates
+- **[Creating Plugins](#)** — Use configuration in custom plugins
diff --git a/docs/03-reference/02-metadata.md b/docs/03-reference/02-metadata.md
new file mode 100644
index 0000000..7f8d806
--- /dev/null
+++ b/docs/03-reference/02-metadata.md
@@ -0,0 +1,447 @@
+# Metadata Reference
+
+Metadata files (`metadata.ini`) configure individual pages and directories. They control titles, URLs, templates, navigation, and more.
+
+## Basic Structure
+
+```ini
+title = "Page Title"
+summary = "Short description"
+date = "2024-12-15"
+search_description = "SEO-friendly description"
+```
+
+Place `metadata.ini` in any content directory:
+
+```
+content/blog/my-post/
+├── index.md
+└── metadata.ini
+```
+
+## Core Fields
+
+### `title`
+
+The page or item title.
+
+```ini
+title = "My Blog Post"
+```
+
+**Default:** Extracted from first `# Heading` in Markdown, or folder name
+**Used in:** Page ``, list items, navigation menu
+**Type:** String
+
+### `summary`
+
+Short description shown in list views.
+
+```ini
+summary = "A brief introduction to this topic"
+```
+
+**Default:** None (empty)
+**Used in:** List item previews, RSS feeds, social media cards
+**Type:** String
+**Recommended length:** 150-200 characters
+
+### `date`
+
+Publication or modification date.
+
+```ini
+date = "2024-12-15"
+```
+
+**Default:** Extracted from folder name (`YYYY-MM-DD-title`)
+**Format:** `YYYY-MM-DD` (ISO 8601)
+**Used in:** List sorting, date displays, `` elements
+**Type:** String (date)
+
+### `search_description`
+
+SEO meta description for search engines.
+
+```ini
+search_description = "Learn how to build fast, maintainable websites with FolderWeb"
+```
+
+**Default:** Uses `summary` if not set
+**Used in:** ` ` tag
+**Type:** String
+**Recommended length:** 150-160 characters
+
+### `slug`
+
+Custom URL slug (overrides folder name).
+
+```ini
+slug = "custom-url"
+```
+
+**Default:** Folder name (with date prefix removed)
+**Used in:** URL generation
+**Type:** String (alphanumeric, hyphens, underscores)
+
+**Example:**
+```
+Folder: content/blog/2024-12-15-very-long-title/
+Slug: short-title
+URL: /blog/short-title/
+```
+
+### `menu`
+
+Show this page in the navigation menu.
+
+```ini
+menu = 1
+```
+
+**Default:** `0` (not in menu)
+**Values:** `1` (show) or `0` (hide)
+**Type:** Integer
+
+### `menu_order`
+
+Position in navigation menu (lower numbers first).
+
+```ini
+menu = 1
+menu_order = 10
+```
+
+**Default:** `999` (last)
+**Used in:** Navigation sorting
+**Type:** Integer
+
+**Example:**
+```ini
+# Home
+menu = 1
+menu_order = 1
+
+# About
+menu = 1
+menu_order = 10
+
+# Blog
+menu = 1
+menu_order = 20
+
+# Contact
+menu = 1
+menu_order = 30
+```
+
+## Settings Section
+
+Advanced settings go in a `[settings]` section:
+
+```ini
+title = "My Page"
+
+[settings]
+page_template = "list-grid"
+show_date = true
+hide_list = false
+```
+
+### `page_template`
+
+Which template to use for list views.
+
+```ini
+[settings]
+page_template = "list-grid"
+```
+
+**Default:** `list` (uses `list.php`)
+**Available templates:**
+- `list` — Simple vertical list
+- `list-grid` — Card grid layout
+- `list-compact` — Minimal compact list
+- Custom templates you create
+
+**Used in:** List view rendering
+**Type:** String (template name without `.php`)
+
+### `show_date`
+
+Display the date on the page.
+
+```ini
+[settings]
+show_date = true
+```
+
+**Default:** `true`
+**Values:** `true` or `false`
+**Type:** Boolean
+
+### `hide_list`
+
+Don't show list view even if directory has subdirectories.
+
+```ini
+[settings]
+hide_list = true
+```
+
+**Default:** `false`
+**Values:** `true` or `false`
+**Type:** Boolean
+**Use case:** Section landing pages that should show content instead of list
+
+## Language-Specific Overrides
+
+Add language-specific sections to override fields:
+
+```ini
+title = "About Us"
+summary = "Learn about our company"
+slug = "about"
+
+[no]
+title = "Om oss"
+summary = "Les om bedriften vår"
+slug = "om"
+
+[de]
+title = "Über uns"
+summary = "Erfahren Sie mehr über unser Unternehmen"
+slug = "uber-uns"
+```
+
+**Supported fields in language sections:**
+- `title`
+- `summary`
+- `search_description`
+- `slug`
+
+**Language codes:** Must match your configured languages (`config.ini`).
+
+**URLs with language-specific slugs:**
+- English: `/about/`
+- Norwegian: `/no/om/`
+- German: `/de/uber-uns/`
+
+## Custom Fields
+
+Add any custom fields you need:
+
+```ini
+title = "Project X"
+author = "Jane Doe"
+client = "ACME Corp"
+project_url = "https://example.com"
+featured = true
+tags = "web,design,portfolio"
+```
+
+Access custom fields in templates:
+
+```php
+
+ By = htmlspecialchars($metadata['author']) ?>
+
+
+
+
+ View Project
+
+
+```
+
+Or in plugins:
+
+```php
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ $metadata = $ctx->get('metadata', []);
+
+ if (isset($metadata['tags'])) {
+ $vars['tags'] = explode(',', $metadata['tags']);
+ }
+
+ return $vars;
+});
+```
+
+## Complete Example
+
+**content/blog/2024-12-15-building-fast-sites/metadata.ini:**
+
+```ini
+# Core fields
+title = "Building Fast Websites"
+summary = "Learn how to optimize your site for speed and performance"
+date = "2024-12-15"
+search_description = "A comprehensive guide to building fast, performant websites in 2024"
+
+# Navigation
+menu = 0 # Don't show in main menu
+menu_order = 999
+
+# Custom slug
+slug = "fast-sites"
+
+# Settings
+[settings]
+show_date = true
+
+# Custom fields
+author = "Jane Doe"
+category = "Web Performance"
+tags = "performance,optimization,web"
+estimated_reading_time = 8
+
+# Norwegian translation
+[no]
+title = "Bygge raske nettsider"
+summary = "Lær hvordan du optimaliserer nettstedet ditt for hastighet og ytelse"
+search_description = "En omfattende guide til å bygge raske, effektive nettsteder i 2024"
+slug = "raske-sider"
+```
+
+## Metadata Priority
+
+When determining values, FolderWeb follows this priority:
+
+1. **Language-specific metadata** (e.g., `[no]` section)
+2. **Root metadata** (e.g., `title = "..."`)
+3. **Auto-extracted values** (e.g., first heading, folder date)
+4. **Defaults** (e.g., folder name)
+
+**Example:**
+
+```
+Folder: content/blog/2024-12-15-my-post/
+```
+
+**Title resolution:**
+1. Check `metadata.ini` for `[en] title = "..."`
+2. Check `metadata.ini` for `title = "..."`
+3. Extract from first `# Heading` in Markdown
+4. Use folder name: "my-post"
+
+## Metadata in List Items
+
+When rendering list views, each item receives these metadata fields:
+
+```php
+$item = [
+ 'url' => '/blog/my-post/',
+ 'path' => '/content/blog/2024-12-15-my-post',
+ 'title' => 'My Post',
+ 'summary' => 'Short description',
+ 'date' => '2024-12-15',
+ 'formatted_date' => '15. desember 2024', // Language-specific
+ 'cover_image' => '/blog/my-post/cover.jpg', // If exists
+ // All custom metadata fields...
+ 'author' => 'Jane Doe',
+ 'tags' => 'web,design',
+];
+```
+
+Access in list templates:
+
+```php
+
+
+ = htmlspecialchars($item['title']) ?>
+
+
+ By = htmlspecialchars($item['author']) ?>
+
+
+
+ = htmlspecialchars($item['summary']) ?>
+
+
+
+```
+
+## Best Practices
+
+### 1. Use Consistent Field Names
+
+```ini
+# Good: consistent naming
+author = "Jane Doe"
+published_date = "2024-12-15"
+featured = true
+
+# Bad: inconsistent naming
+Author = "Jane Doe"
+PublishDate = "2024-12-15"
+is_featured = true
+```
+
+### 2. Keep Summaries Concise
+
+```ini
+# Good
+summary = "Learn to optimize website performance in 5 steps"
+
+# Too long
+summary = "This comprehensive article will teach you everything you need to know about optimizing website performance, including lazy loading, code splitting, image optimization, and much more in detailed steps with examples"
+```
+
+### 3. Use Semantic Custom Fields
+
+```ini
+# Good: clear purpose
+author = "Jane Doe"
+category = "Tutorial"
+difficulty = "Beginner"
+
+# Bad: unclear purpose
+field1 = "Jane Doe"
+field2 = "Tutorial"
+field3 = "Beginner"
+```
+
+### 4. Add Comments
+
+```ini
+# SEO and social media
+title = "Building Fast Websites"
+search_description = "A guide to web performance optimization"
+
+# Author and categorization
+author = "Jane Doe"
+category = "Performance"
+
+# Custom display options
+featured = true # Show in featured section
+priority = 10 # Higher = more prominent
+```
+
+## Debugging Metadata
+
+To see parsed metadata, create a debug template:
+
+**custom/templates/page.php:**
+
+```php
+
+
+= $content ?>
+```
+
+Or in list templates:
+
+```php
+
+
+= $pageContent ?>
+```
+
+**Remember to revert** before deploying to production.
+
+## What's Next?
+
+- **[Template Variables](#)** — See how metadata is used in templates
+- **[Internationalization](#)** — Use language-specific metadata
+- **[Creating Plugins](#)** — Process metadata in custom plugins
diff --git a/docs/03-reference/03-template-variables.md b/docs/03-reference/03-template-variables.md
new file mode 100644
index 0000000..cd715bc
--- /dev/null
+++ b/docs/03-reference/03-template-variables.md
@@ -0,0 +1,474 @@
+# Template Variables Reference
+
+Templates have access to a set of variables provided by FolderWeb and its plugins. This reference documents all available variables and their types.
+
+## Base Template Variables
+
+Available in `base.php`:
+
+### `$content`
+
+The fully rendered HTML content from the page or list template.
+
+**Type:** String (HTML)
+**Example:**
+```php
+
+ = $content ?>
+
+```
+
+### `$pageTitle`
+
+The page title for the `` tag.
+
+**Type:** String
+**Default:** `"FolderWeb"`
+**Example:**
+```php
+= htmlspecialchars($pageTitle ?? 'FolderWeb') ?>
+```
+
+**Source:**
+1. Language-specific metadata `[lang] title`
+2. Root metadata `title`
+3. First heading in content
+4. Folder name
+
+### `$metaDescription`
+
+SEO description for the ` ` tag.
+
+**Type:** String
+**Optional:** May be empty
+**Example:**
+```php
+
+
+
+```
+
+**Source:**
+1. Metadata `search_description`
+2. Metadata `summary`
+3. Empty if not set
+
+### `$socialImageUrl`
+
+URL to cover image for social media meta tags.
+
+**Type:** String (URL)
+**Optional:** May be empty
+**Example:**
+```php
+
+
+
+```
+
+**Source:** First `cover.*` image found in content directory
+
+### `$navigation`
+
+Array of navigation menu items.
+
+**Type:** Array of associative arrays
+**Structure:**
+```php
+[
+ ['url' => '/about/', 'title' => 'About'],
+ ['url' => '/blog/', 'title' => 'Blog'],
+ ['url' => '/contact/', 'title' => 'Contact'],
+]
+```
+
+**Example:**
+```php
+
+
+
+
+ = htmlspecialchars($item['title']) ?>
+
+
+
+
+```
+
+**Source:** Pages with `menu = 1` in metadata, sorted by `menu_order`
+
+### `$homeLabel`
+
+Text for the home link.
+
+**Type:** String
+**Default:** `"Home"`
+**Example:**
+```php
+= htmlspecialchars($homeLabel ?? 'Home') ?>
+```
+
+**Source:** Translation string `translations['home']` or fallback "Home"
+
+### `$currentLang`
+
+Current language code.
+
+**Type:** String
+**Default:** From `config.ini` `languages.default`
+**Example:**
+```php
+
+```
+
+**Values:** ISO 639-1 language codes (`en`, `no`, `de`, etc.)
+
+### `$langPrefix`
+
+URL prefix for the current language.
+
+**Type:** String
+**Default:** Empty string for default language
+**Example:**
+```php
+Home
+```
+
+**Values:**
+- `""` (empty) for default language
+- `"/no"` for Norwegian
+- `"/de"` for German
+- etc.
+
+### `$languageUrls`
+
+URLs to switch between available languages.
+
+**Type:** Associative array
+**Structure:**
+```php
+[
+ 'en' => '/page/',
+ 'no' => '/no/side/',
+ 'de' => '/de/seite/',
+]
+```
+
+**Example:**
+```php
+ 1): ?>
+
+ $url): ?>
+ >
+ = htmlspecialchars(strtoupper($lang)) ?>
+
+
+
+
+```
+
+### `$translations`
+
+Translated UI strings for the current language.
+
+**Type:** Associative array
+**Structure:**
+```php
+[
+ 'home' => 'Home',
+ 'footer_handcoded' => 'Generated in',
+ 'footer_page_time' => 'ms',
+ 'months' => 'January,February,March,...',
+]
+```
+
+**Example:**
+```php
+= htmlspecialchars($translations['home'] ?? 'Home') ?>
+```
+
+**Source:** Language files in `custom/languages/[lang].ini` or `app/default/languages/[lang].ini`
+
+### `$pageCssUrl`
+
+URL to page-specific CSS file.
+
+**Type:** String (URL)
+**Optional:** Only set if `styles.css` exists in content directory
+**Example:**
+```php
+
+
+
+```
+
+### `$pageCssHash`
+
+MD5 hash of page-specific CSS for cache busting.
+
+**Type:** String (MD5 hash)
+**Optional:** Only set if `$pageCssUrl` exists
+**Example:** See `$pageCssUrl` above
+
+## Page Template Variables
+
+Available in `page.php`:
+
+### `$content`
+
+The fully rendered HTML content from the page.
+
+**Type:** String (HTML)
+**Example:**
+```php
+
+ = $content ?>
+
+```
+
+### `$metadata`
+
+All metadata for the current page.
+
+**Type:** Associative array
+**Structure:**
+```php
+[
+ 'title' => 'Page Title',
+ 'summary' => 'Short description',
+ 'date' => '2024-12-15',
+ 'formatted_date' => '15. desember 2024',
+ 'show_date' => true,
+ 'author' => 'Jane Doe', // Custom fields
+ 'tags' => 'web,design',
+ // ... all other metadata fields
+]
+```
+
+**Example:**
+```php
+
+ = htmlspecialchars($metadata['title']) ?>
+
+
+
+
+ = $metadata['formatted_date'] ?? $metadata['date'] ?>
+
+
+```
+
+## List Template Variables
+
+Available in `list.php`, `list-grid.php`, `list-compact.php`, etc.:
+
+### `$pageContent`
+
+Optional intro content from the directory's own files.
+
+**Type:** String (HTML)
+**Optional:** May be empty
+**Example:**
+```php
+
+
+ = $pageContent ?>
+
+
+```
+
+**Source:** Content files in the list directory itself (not subdirectories)
+
+### `$items`
+
+Array of items to display in the list.
+
+**Type:** Array of associative arrays
+**Structure:**
+```php
+[
+ [
+ 'url' => '/blog/my-post/',
+ 'path' => '/content/blog/2024-12-15-my-post',
+ 'title' => 'My Post',
+ 'summary' => 'Short description',
+ 'date' => '2024-12-15',
+ 'formatted_date' => '15. desember 2024',
+ 'cover_image' => '/blog/my-post/cover.jpg',
+ // All custom metadata fields...
+ 'author' => 'Jane Doe',
+ 'category' => 'Tutorial',
+ ],
+ // ... more items
+]
+```
+
+**Example:**
+```php
+
+
+
+
+
+
+
+
+
+
+ = $item['formatted_date'] ?? $item['date'] ?>
+
+
+
+
+ = htmlspecialchars($item['summary']) ?>
+
+
+
+```
+
+### `$metadata`
+
+Metadata for the list directory itself.
+
+**Type:** Associative array
+**Structure:** Same as page metadata
+**Example:**
+```php
+
+ = htmlspecialchars($metadata['title']) ?>
+
+```
+
+## Item Properties
+
+Each item in `$items` has these properties:
+
+| Property | Type | Description | Optional |
+|----------|------|-------------|----------|
+| `url` | String | Full URL to the item | No |
+| `path` | String | Filesystem path to item | No |
+| `title` | String | Item title | No |
+| `summary` | String | Short description | Yes |
+| `date` | String | ISO date (YYYY-MM-DD) | Yes |
+| `formatted_date` | String | Localized date string | Yes |
+| `cover_image` | String | URL to cover image | Yes |
+| Custom fields | Mixed | Any metadata fields | Yes |
+
+## Adding Custom Variables
+
+Use the `Hook::TEMPLATE_VARS` hook to add custom variables:
+
+```php
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ // Add a custom variable
+ $vars['siteName'] = 'My Website';
+
+ // Add computed values
+ $vars['currentYear'] = date('Y');
+
+ // Add from context
+ $vars['userName'] = $ctx->get('user_name', 'Guest');
+
+ return $vars;
+});
+```
+
+Then use in templates:
+
+```php
+© = $currentYear ?> = htmlspecialchars($siteName) ?>
+```
+
+## Variable Availability by Template
+
+| Variable | `base.php` | `page.php` | `list.php` |
+|----------|------------|------------|------------|
+| `$content` | ✓ | ✓ | — |
+| `$pageTitle` | ✓ | — | — |
+| `$metaDescription` | ✓ | — | — |
+| `$socialImageUrl` | ✓ | — | — |
+| `$navigation` | ✓ | — | — |
+| `$homeLabel` | ✓ | — | — |
+| `$currentLang` | ✓ | — | — |
+| `$langPrefix` | ✓ | — | — |
+| `$languageUrls` | ✓ | — | — |
+| `$translations` | ✓ | — | — |
+| `$pageCssUrl` | ✓ | — | — |
+| `$pageCssHash` | ✓ | — | — |
+| `$metadata` | — | ✓ | ✓ |
+| `$pageContent` | — | — | ✓ |
+| `$items` | — | — | ✓ |
+
+**Note:** All variables are technically available everywhere via plugin hooks, but this table shows the default availability.
+
+## Escaping Output
+
+**Always escape user content** to prevent XSS attacks:
+
+```php
+
+= htmlspecialchars($metadata['title']) ?>
+= htmlspecialchars($item['summary']) ?>
+
+
+= $metadata['title'] ?>
+= $item['summary'] ?>
+```
+
+**Exception:** Already-sanitized HTML like `$content` (rendered from Markdown):
+
+```php
+
+
+ = $content ?>
+
+```
+
+## Checking Variable Existence
+
+Always check if optional variables exist:
+
+```php
+
+
+ By = htmlspecialchars($metadata['author']) ?>
+
+
+
+By = htmlspecialchars($metadata['author']) ?>
+```
+
+Use null coalescing for defaults:
+
+```php
+= htmlspecialchars($metadata['author'] ?? 'Anonymous') ?>
+```
+
+## Debugging Variables
+
+To see all available variables in a template:
+
+```php
+
+```
+
+Or specific variables:
+
+```php
+
+
+```
+
+**Remember to remove debug code** before deploying to production.
+
+## What's Next?
+
+- **[Internationalization](#)** — Use language-specific variables
+- **[Creating Plugins](#)** — Add custom template variables
+- **[Template Tutorial](#)** — See variables in action
diff --git a/docs/03-reference/04-internationalization.md b/docs/03-reference/04-internationalization.md
new file mode 100644
index 0000000..7b7d830
--- /dev/null
+++ b/docs/03-reference/04-internationalization.md
@@ -0,0 +1,526 @@
+# Internationalization (i18n)
+
+FolderWeb supports multilingual websites through the built-in `languages` plugin. This guide covers everything you need to build sites in multiple languages.
+
+## How It Works
+
+The language plugin provides URL-based language selection:
+
+- **Default language:** `/about/` (no language prefix)
+- **Other languages:** `/no/om/`, `/de/uber-uns/`
+
+Language is determined from the URL, and content files, metadata, and translations adapt automatically.
+
+## Configuration
+
+Enable and configure languages in `custom/config.ini`:
+
+```ini
+[languages]
+default = "en" # Default language (no URL prefix)
+available = "en,no,de" # Comma-separated language codes
+
+[plugins]
+enabled = "languages" # Enable the language plugin
+```
+
+**Language codes:** Use ISO 639-1 two-letter codes (`en`, `no`, `de`, `fr`, `es`, etc.).
+
+## Language-Specific Content Files
+
+Create language variants of content files using the naming pattern `name.lang.ext`:
+
+```
+content/about/
+├── index.md # Default language (English)
+├── index.no.md # Norwegian version
+└── index.de.md # German version
+```
+
+**How it works:**
+- URL `/about/` → Shows `index.md`
+- URL `/no/om/` → Shows `index.no.md`
+- URL `/de/uber-uns/` → Shows `index.de.md`
+
+**Fallback behavior:** If no language-specific file exists, the default file is shown.
+
+### Multiple Files Per Page
+
+Language variants work with multiple content files:
+
+```
+content/portfolio/
+├── 00-hero.php
+├── 00-hero.no.php
+├── 01-intro.md
+├── 01-intro.no.md
+├── 02-projects.html
+└── 02-projects.no.html
+```
+
+- URL `/portfolio/` → Shows `00-hero.php` + `01-intro.md` + `02-projects.html`
+- URL `/no/portfolio/` → Shows `00-hero.no.php` + `01-intro.no.md` + `02-projects.no.html`
+
+## Language-Specific Metadata
+
+Override metadata fields for each language using sections in `metadata.ini`:
+
+```ini
+# Default (English)
+title = "About Us"
+summary = "Learn about our company"
+slug = "about"
+
+# Norwegian
+[no]
+title = "Om oss"
+summary = "Les om bedriften vår"
+slug = "om"
+
+# German
+[de]
+title = "Über uns"
+summary = "Erfahren Sie mehr über unser Unternehmen"
+slug = "uber-uns"
+```
+
+**Supported fields:**
+- `title` — Page/item title
+- `summary` — Short description
+- `search_description` — SEO description
+- `slug` — Custom URL slug
+
+**Result:**
+- `/about/` — Title: "About Us"
+- `/no/om/` — Title: "Om oss"
+- `/de/uber-uns/` — Title: "Über uns"
+
+## Translation Files
+
+UI strings (home link, footer text, month names) are translated using language files:
+
+```
+custom/languages/
+├── en.ini
+├── no.ini
+└── de.ini
+```
+
+### Creating Translation Files
+
+**custom/languages/en.ini:**
+
+```ini
+home = "Home"
+footer_handcoded = "Generated in"
+footer_page_time = "ms"
+months = "January,February,March,April,May,June,July,August,September,October,November,December"
+```
+
+**custom/languages/no.ini:**
+
+```ini
+home = "Hjem"
+footer_handcoded = "Generert på"
+footer_page_time = "ms"
+months = "januar,februar,mars,april,mai,juni,juli,august,september,oktober,november,desember"
+```
+
+**custom/languages/de.ini:**
+
+```ini
+home = "Startseite"
+footer_handcoded = "Generiert in"
+footer_page_time = "ms"
+months = "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember"
+```
+
+### Using Translations in Templates
+
+Access translations via the `$translations` variable:
+
+```php
+
+ = htmlspecialchars($translations['home'] ?? 'Home') ?>
+
+
+
+```
+
+### Adding Custom Translation Strings
+
+Add any strings you need:
+
+**custom/languages/en.ini:**
+
+```ini
+read_more = "Read more"
+posted_on = "Posted on"
+by_author = "by"
+categories = "Categories"
+tags = "Tags"
+```
+
+**custom/languages/no.ini:**
+
+```ini
+read_more = "Les mer"
+posted_on = "Publisert"
+by_author = "av"
+categories = "Kategorier"
+tags = "Tagger"
+```
+
+Use in templates:
+
+```php
+
+ = htmlspecialchars($translations['read_more'] ?? 'Read more') ?> →
+
+
+
+ = htmlspecialchars($translations['posted_on'] ?? 'Posted on') ?>
+ = $item['formatted_date'] ?>
+
+```
+
+## Language Switcher
+
+The language plugin automatically provides language switcher URLs in the `$languageUrls` variable.
+
+**In base.php:**
+
+```php
+ 1): ?>
+
+ $url): ?>
+ >
+ = htmlspecialchars(strtoupper($lang)) ?>
+
+
+
+
+```
+
+**How it works:**
+- The switcher links to the **same page** in different languages
+- Language-specific slugs are automatically resolved
+- Current language is marked with `aria-current="true"`
+
+**Example URLs:**
+- On `/about/`: EN → `/about/`, NO → `/no/om/`, DE → `/de/uber-uns/`
+- On `/no/om/`: EN → `/about/`, NO → `/no/om/`, DE → `/de/uber-uns/`
+
+## Date Formatting
+
+Dates are automatically formatted using translated month names.
+
+**With `months` in language files:**
+
+```ini
+# en.ini
+months = "January,February,March,April,May,June,July,August,September,October,November,December"
+
+# no.ini
+months = "januar,februar,mars,april,mai,juni,juli,august,september,oktober,november,desember"
+```
+
+**Result:**
+- English: "15. December 2024"
+- Norwegian: "15. desember 2024"
+
+**Date format:** `[day]. [month] [year]` (e.g., "15. December 2024")
+
+## Complete Multilingual Example
+
+### Directory Structure
+
+```
+content/
+├── metadata.ini
+├── index.md
+├── index.no.md
+└── blog/
+ ├── metadata.ini
+ ├── 2024-12-15-first-post/
+ │ ├── metadata.ini
+ │ ├── index.md
+ │ ├── index.no.md
+ │ └── cover.jpg
+ └── 2024-12-20-second-post/
+ ├── metadata.ini
+ ├── index.md
+ └── index.no.md
+```
+
+### Root Metadata
+
+**content/metadata.ini:**
+
+```ini
+title = "My Site"
+
+[no]
+title = "Min Side"
+```
+
+### Blog Metadata
+
+**content/blog/metadata.ini:**
+
+```ini
+title = "Blog"
+summary = "Latest articles and updates"
+
+[no]
+title = "Blogg"
+summary = "Siste artikler og oppdateringer"
+```
+
+### Post Metadata
+
+**content/blog/2024-12-15-first-post/metadata.ini:**
+
+```ini
+title = "My First Post"
+summary = "An introduction to my blog"
+slug = "first-post"
+
+[no]
+title = "Mitt første innlegg"
+summary = "En introduksjon til bloggen min"
+slug = "forste-innlegg"
+```
+
+### URLs Generated
+
+**English (default):**
+- Home: `/`
+- Blog: `/blog/`
+- Post: `/blog/first-post/`
+
+**Norwegian:**
+- Home: `/no/`
+- Blog: `/no/blogg/`
+- Post: `/no/blogg/forste-innlegg/`
+
+## Language-Aware Navigation
+
+Navigation menus automatically use language-specific titles:
+
+**content/about/metadata.ini:**
+
+```ini
+title = "About"
+menu = 1
+menu_order = 10
+
+[no]
+title = "Om"
+```
+
+**Result in navigation:**
+- English site: "About"
+- Norwegian site: "Om"
+
+## Template Variables for i18n
+
+The language plugin provides these template variables:
+
+| Variable | Type | Description |
+|----------|------|-------------|
+| `$currentLang` | String | Current language code (e.g., `"en"`, `"no"`) |
+| `$defaultLang` | String | Default language from config |
+| `$langPrefix` | String | URL prefix (e.g., `""`, `"/no"`) |
+| `$languageUrls` | Array | URLs to switch languages |
+| `$translations` | Array | Translated UI strings |
+| `$availableLangs` | Array | All available language codes |
+
+**Example usage:**
+
+```php
+
+
+
+ = htmlspecialchars($translations['home'] ?? 'Home') ?>
+
+
+
+
+
+ = htmlspecialchars($item['title']) ?>
+
+
+
+```
+
+## Right-to-Left (RTL) Languages
+
+For RTL languages (Arabic, Hebrew, etc.), set the `dir` attribute:
+
+**custom/templates/base.php:**
+
+```php
+
+
+```
+
+Use logical CSS properties for proper RTL support:
+
+```css
+/* Good: logical properties */
+.card {
+ margin-inline-start: 1rem;
+ padding-inline-end: 2rem;
+}
+
+/* Bad: directional properties */
+.card {
+ margin-left: 1rem;
+ padding-right: 2rem;
+}
+```
+
+## Best Practices
+
+### 1. Always Provide Fallbacks
+
+```php
+
+= htmlspecialchars($translations['read_more'] ?? 'Read more') ?>
+
+
+= htmlspecialchars($translations['read_more']) ?>
+```
+
+### 2. Use Language Codes Consistently
+
+```ini
+# Good
+[languages]
+available = "en,no,de" # Lowercase, ISO 639-1
+
+# Bad
+available = "EN,nb-NO,de-DE" # Mixed case, non-standard
+```
+
+### 3. Translate Everything
+
+Don't mix languages on the same page:
+
+```php
+
+= htmlspecialchars($translations['posted_on']) ?> = $item['formatted_date'] ?>
+
+
+Posted on = $item['formatted_date'] ?>
+```
+
+### 4. Test All Languages
+
+Verify:
+- Content files load correctly
+- Metadata overrides work
+- Language switcher links are correct
+- Navigation uses translated titles
+- Dates format properly
+
+### 5. Handle Missing Translations Gracefully
+
+```php
+
+ = htmlspecialchars($item['summary']) ?>
+
+ = htmlspecialchars($translations['no_summary'] ?? 'No description available') ?>
+
+```
+
+## Limitations
+
+### No Automatic Translation
+
+FolderWeb doesn't translate content automatically. You must:
+- Create separate content files for each language
+- Manually translate all metadata
+- Provide all translation strings
+
+### No Language Detection
+
+FolderWeb doesn't detect browser language. Users must:
+- Click the language switcher
+- Visit a language-specific URL directly
+
+You can add browser detection with a custom plugin if needed.
+
+### Fixed URL Structure
+
+All languages share the same folder structure. You cannot have:
+- Content in `/en/blog/` and `/no/nyheter/` (different folder names)
+
+You must use:
+- Content in `/blog/` with language-specific slugs and content files
+
+## Troubleshooting
+
+### Language Switcher Shows Wrong URLs
+
+**Problem:** Language switcher links to incorrect pages.
+
+**Solution:** Check that language-specific slugs are set in metadata:
+
+```ini
+slug = "about"
+
+[no]
+slug = "om" # Must be set
+```
+
+### Content Not Changing Language
+
+**Problem:** Same content appears in all languages.
+
+**Solution:** Verify file naming:
+- ✓ `index.no.md` (correct)
+- ✗ `index-no.md` (wrong)
+- ✗ `index_no.md` (wrong)
+
+### Dates Not Translating
+
+**Problem:** Dates show in English for all languages.
+
+**Solution:** Add `months` to language files:
+
+```ini
+months = "January,February,March,April,May,June,July,August,September,October,November,December"
+```
+
+### Navigation Shows English Titles
+
+**Problem:** Menu items use English even in other languages.
+
+**Solution:** Add language sections to metadata:
+
+```ini
+title = "About"
+menu = 1
+
+[no]
+title = "Om"
+```
+
+## What's Next?
+
+- **[Configuration Reference](#)** — Configure available languages
+- **[Metadata Reference](#)** — Set language-specific metadata
+- **[Template Variables](#)** — Use i18n variables in templates
+- **[Creating Plugins](#)** — Extend i18n functionality
diff --git a/docs/04-development/01-plugin-system.md b/docs/04-development/01-plugin-system.md
new file mode 100644
index 0000000..b5020c8
--- /dev/null
+++ b/docs/04-development/01-plugin-system.md
@@ -0,0 +1,648 @@
+# Plugin System
+
+FolderWeb uses a minimal hook-based plugin system for extensibility. Plugins let you modify content, add functionality, and inject custom variables into templates—all without touching the framework code.
+
+## How Plugins Work
+
+Plugins are PHP files that register callbacks with one or more **hooks**:
+
+1. **`Hook::CONTEXT_READY`** — After context is created, before routing
+2. **`Hook::PROCESS_CONTENT`** — When loading/processing content
+3. **`Hook::TEMPLATE_VARS`** — Before rendering templates
+
+Each hook receives data, allows modification, and returns the modified data.
+
+## Plugin Locations
+
+```
+app/plugins/
+├── global/ # Built-in global plugins (don't modify)
+│ └── languages.php
+└── page/ # Built-in page plugins (empty by default)
+
+custom/plugins/
+├── global/ # Your global plugins
+│ ├── analytics.php
+│ └── reading-time.php
+└── page/ # Your page plugins (not yet used)
+```
+
+**Global plugins:** Loaded on every request
+**Page plugins:** Reserved for future use
+
+## Enabling Plugins
+
+List enabled plugins in `custom/config.ini`:
+
+```ini
+[plugins]
+enabled = "languages,analytics,reading-time"
+```
+
+Plugin names correspond to filenames without `.php`:
+- `languages` → `languages.php`
+- `analytics` → `analytics.php`
+- `reading-time` → `reading-time.php`
+
+FolderWeb loads plugins from:
+1. `app/plugins/global/` (built-in)
+2. `custom/plugins/global/` (yours)
+
+## The Three Hooks
+
+### `Hook::CONTEXT_READY`
+
+Called after the context object is created, before routing begins.
+
+**Use for:**
+- Setting global context values
+- Processing configuration
+- Adding cross-cutting concerns
+
+**Signature:**
+
+```php
+Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
+ // Modify context
+ $ctx->set('key', 'value');
+
+ // Must return context
+ return $ctx;
+});
+```
+
+**Parameters:**
+- `$ctx` — Context object (see [Context API](#context-api))
+- `$config` — Merged configuration array from `config.ini`
+
+**Must return:** Modified `$ctx`
+
+### `Hook::PROCESS_CONTENT`
+
+Called when loading or processing content (files, metadata, dates).
+
+**Use for:**
+- Filtering content files
+- Transforming metadata
+- Custom content processing
+
+**Signature:**
+
+```php
+Hooks::add(Hook::PROCESS_CONTENT, function(mixed $data, string $dirOrType, string $extraContext = '') {
+ // Process data based on type
+ if ($extraContext === 'metadata') {
+ // Modify metadata array
+ $data['custom_field'] = 'value';
+ }
+
+ // Must return data
+ return $data;
+});
+```
+
+**Parameters:**
+- `$data` — The data being processed (type varies)
+- `$dirOrType` — Directory path or processing type
+- `$extraContext` — Additional context (e.g., `"metadata"`, `"date_format"`)
+
+**Must return:** Modified `$data`
+
+**Common `$extraContext` values:**
+- `"metadata"` — Processing metadata array
+- `"date_format"` — Formatting a date string
+
+### `Hook::TEMPLATE_VARS`
+
+Called before rendering templates, allowing you to add variables.
+
+**Use for:**
+- Adding custom template variables
+- Computing values for display
+- Injecting data into templates
+
+**Signature:**
+
+```php
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ // Add custom variables
+ $vars['siteName'] = 'My Website';
+ $vars['currentYear'] = date('Y');
+
+ // Must return vars
+ return $vars;
+});
+```
+
+**Parameters:**
+- `$vars` — Array of template variables
+- `$ctx` — Context object
+
+**Must return:** Modified `$vars` array
+
+## Context API
+
+The `Context` object stores global state. Access it in hooks:
+
+```php
+// Set a value
+$ctx->set('key', 'value');
+
+// Get a value
+$value = $ctx->get('key');
+
+// Get with default
+$value = $ctx->get('key', 'default');
+
+// Check if exists
+if ($ctx->has('key')) {
+ // ...
+}
+```
+
+**Built-in context values:**
+
+| Key | Type | Description |
+|-----|------|-------------|
+| `requestPath` | String | URL path (e.g., `"blog/my-post"`) |
+| `contentDir` | String | Filesystem path to content |
+| `currentLang` | String | Current language (from languages plugin) |
+| `defaultLang` | String | Default language |
+| `translations` | Array | Translated strings |
+| `metadata` | Array | Current page metadata |
+
+## Creating Your First Plugin
+
+Let's create a plugin that adds a reading time estimate to posts.
+
+### Step 1: Create the Plugin File
+
+**custom/plugins/global/reading-time.php:**
+
+```php
+
+
+
+
+ = $content ?>
+
+
+```
+
+Done! Every page now shows reading time.
+
+## Plugin Examples
+
+### Analytics Plugin
+
+Add Google Analytics tracking ID to all pages.
+
+**custom/plugins/global/analytics.php:**
+
+```php
+
+
+
+
+
+
+
+
+```
+
+### Table of Contents Plugin
+
+Generate a table of contents from headings.
+
+**custom/plugins/global/toc.php:**
+
+```php
+(.*?)<\/h\1>/i', $content, $matches)) {
+ foreach ($matches[0] as $i => $match) {
+ $level = (int)$matches[1][$i];
+ $text = strip_tags($matches[2][$i]);
+ $id = slugify($text);
+
+ // Add ID to heading
+ $newHeading = str_replace('', '', $match);
+ $content = str_replace($match, $newHeading, $content);
+
+ $toc[] = [
+ 'level' => $level,
+ 'text' => $text,
+ 'id' => $id,
+ ];
+ }
+ }
+
+ $vars['content'] = $content;
+ $vars['tableOfContents'] = $toc;
+
+ return $vars;
+});
+
+function slugify(string $text): string {
+ $text = strtolower($text);
+ $text = preg_replace('/[^a-z0-9]+/', '-', $text);
+ return trim($text, '-');
+}
+```
+
+**Use in template:**
+
+```php
+
+
+ Table of Contents
+
+
+
+
+
+ = $content ?>
+
+```
+
+### Author Bio Plugin
+
+Add author information from metadata.
+
+**custom/plugins/global/author-bio.php:**
+
+```php
+get('metadata', []);
+
+ // Load author data if specified
+ if (isset($metadata['author'])) {
+ $authorSlug = slugify($metadata['author']);
+ $authorFile = dirname(__DIR__, 2) . "/content/authors/$authorSlug.ini";
+
+ if (file_exists($authorFile)) {
+ $authorData = parse_ini_file($authorFile);
+ $vars['authorBio'] = $authorData;
+ }
+ }
+
+ return $vars;
+});
+
+function slugify(string $text): string {
+ return strtolower(preg_replace('/[^a-z0-9]+/', '-', $text));
+}
+```
+
+**content/authors/jane-doe.ini:**
+
+```ini
+name = "Jane Doe"
+bio = "Web developer and writer"
+email = "jane@example.com"
+twitter = "@janedoe"
+website = "https://janedoe.com"
+```
+
+**Use in template:**
+
+```php
+
+
+
+```
+
+### Related Posts Plugin
+
+Show related posts based on tags.
+
+**custom/plugins/global/related-posts.php:**
+
+```php
+get('metadata', []);
+
+ // Only for pages with tags
+ if (!isset($metadata['tags'])) {
+ return $vars;
+ }
+
+ $currentPath = $ctx->get('currentPath', '');
+ $currentTags = array_map('trim', explode(',', $metadata['tags']));
+
+ // Find other posts with similar tags
+ $contentDir = $ctx->contentDir;
+ $relatedPosts = findRelatedPosts($contentDir, $currentPath, $currentTags);
+
+ if (!empty($relatedPosts)) {
+ $vars['relatedPosts'] = $relatedPosts;
+ }
+
+ return $vars;
+});
+
+function findRelatedPosts(string $contentDir, string $currentPath, array $currentTags): array {
+ $posts = [];
+
+ // Recursively scan content directory
+ $iterator = new RecursiveIteratorIterator(
+ new RecursiveDirectoryIterator($contentDir, RecursiveDirectoryIterator::SKIP_DOTS)
+ );
+
+ foreach ($iterator as $file) {
+ if ($file->getFilename() === 'metadata.ini') {
+ $dir = dirname($file->getPathname());
+
+ // Skip current page
+ if ($dir === $currentPath) continue;
+
+ $metadata = parse_ini_file($file->getPathname());
+
+ if (isset($metadata['tags'])) {
+ $tags = array_map('trim', explode(',', $metadata['tags']));
+ $commonTags = array_intersect($currentTags, $tags);
+
+ if (!empty($commonTags)) {
+ $posts[] = [
+ 'title' => $metadata['title'] ?? basename($dir),
+ 'url' => str_replace($contentDir, '', $dir) . '/',
+ 'tags' => $tags,
+ 'relevance' => count($commonTags),
+ ];
+ }
+ }
+ }
+ }
+
+ // Sort by relevance
+ usort($posts, fn($a, $b) => $b['relevance'] <=> $a['relevance']);
+
+ // Return top 3
+ return array_slice($posts, 0, 3);
+}
+```
+
+**Use in template:**
+
+```php
+
+
+
+```
+
+## Best Practices
+
+### 1. Always Return Modified Data
+
+```php
+// Good
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ $vars['custom'] = 'value';
+ return $vars; // Always return
+});
+
+// Bad
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ $vars['custom'] = 'value';
+ // Missing return - breaks other plugins!
+});
+```
+
+### 2. Use Configuration for Settings
+
+```php
+// Good: configurable
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ global $config;
+
+ $wordsPerMinute = $config['reading_time']['words_per_minute'] ?? 200;
+ // Use $wordsPerMinute...
+
+ return $vars;
+});
+```
+
+**custom/config.ini:**
+
+```ini
+[reading_time]
+words_per_minute = 250
+```
+
+### 3. Check Variable Existence
+
+```php
+// Good: defensive
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ if (isset($vars['content'])) {
+ // Process content
+ }
+ return $vars;
+});
+
+// Bad: assumes content exists
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ $wordCount = str_word_count($vars['content']); // May error
+ return $vars;
+});
+```
+
+### 4. Namespace Helper Functions
+
+```php
+// Good: prefixed function name
+function readingTime_calculate(string $content): int {
+ // ...
+}
+
+// Bad: generic name (may conflict)
+function calculate(string $content): int {
+ // ...
+}
+```
+
+### 5. Use Type Hints
+
+```php
+// Good: type hints for clarity
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx): array {
+ $vars['custom'] = 'value';
+ return $vars;
+});
+```
+
+## Debugging Plugins
+
+### Check Plugin Loading
+
+Add debug output to verify your plugin loads:
+
+```php
+
+```
+
+**Remove before deploying to production.**
+
+## Limitations
+
+- **No inter-plugin communication:** Plugins can't directly call each other
+- **Single execution order:** Hooks execute in registration order (no priority system)
+- **Global scope:** Be careful with global variables and function names
+- **No automatic loading:** Plugins must be listed in `config.ini`
+
+## What's Next?
+
+- **[Hook Reference](#)** — Detailed documentation of all hooks
+- **[Example Plugins](#)** — More real-world plugin examples
+- **[Contributing](#)** — Share your plugins with the community
diff --git a/docs/04-development/02-creating-templates.md b/docs/04-development/02-creating-templates.md
new file mode 100644
index 0000000..abd49b6
--- /dev/null
+++ b/docs/04-development/02-creating-templates.md
@@ -0,0 +1,719 @@
+# Creating Custom Templates
+
+Templates control the HTML structure and presentation of your content. This guide covers advanced template creation, from simple page layouts to complex list views.
+
+## Template Hierarchy
+
+FolderWeb uses a three-level template system:
+
+1. **Base template** (`base.php`) — The HTML scaffold wrapping everything
+2. **Content template** — Either `page.php` or a list template
+3. **Partials** (optional) — Reusable components you create
+
+```
+base.php
+└── page.php or list.php
+ └── Rendered content
+```
+
+## Template Resolution
+
+When rendering a page, FolderWeb looks for templates in this order:
+
+**For page views:**
+1. `custom/templates/page.php`
+2. `app/default/templates/page.php` (fallback)
+
+**For list views:**
+1. `custom/templates/{page_template}.php` (e.g., `list-grid.php`)
+2. `custom/templates/list.php`
+3. `app/default/templates/{page_template}.php`
+4. `app/default/templates/list.php` (fallback)
+
+**For base:**
+1. `custom/templates/base.php`
+2. `app/default/templates/base.php` (fallback)
+
+## Creating a Custom Base Template
+
+The base template defines the HTML structure for every page.
+
+### Step 1: Copy the Default
+
+```bash
+cp app/default/templates/base.php custom/templates/base.php
+```
+
+### Step 2: Customize
+
+**custom/templates/base.php:**
+
+```php
+
+
+
+
+
+ = htmlspecialchars($pageTitle ?? 'My Site') ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Skip to main content
+
+
+
+
+
+ = $content ?>
+
+
+
+
+
+
+```
+
+### Key Features
+
+- **Skip link** for accessibility
+- **Container divs** for layout control
+- **Semantic HTML** (header, nav, main, footer)
+- **ARIA labels** for screen readers
+- **Open Graph tags** for social media
+- **Performance metrics** in footer
+
+## Creating Custom Page Templates
+
+Page templates wrap single-page content.
+
+### Blog Post Template
+
+**custom/templates/page.php:**
+
+```php
+
+
+
+
+ = $content ?>
+
+
+
+
+
+
+```
+
+### Portfolio Item Template
+
+**custom/templates/page-portfolio.php:**
+
+```php
+
+
+
+
+
+
+
+
+
+
+ = $content ?>
+
+
+
+
+
+
+```
+
+**To use:** Set in metadata:
+
+```ini
+[settings]
+page_template = "page-portfolio"
+```
+
+Wait, that won't work for page templates—only list templates use `page_template`. For page templates, you'd need to select via a plugin or use different template files per directory. Let's stick with one `page.php` that adapts based on metadata.
+
+## Creating Custom List Templates
+
+List templates display collections of items.
+
+### Card Grid Layout
+
+**custom/templates/list-cards.php:**
+
+```php
+
+
+ = $pageContent ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = $item['formatted_date'] ?? $item['date'] ?>
+
+
+
+
+
+ = htmlspecialchars($item['summary']) ?>
+
+
+
+
+ Read more →
+
+
+
+
+
+```
+
+**Corresponding CSS:**
+
+```css
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
+ gap: 2rem;
+ margin-top: 2rem;
+}
+
+.card {
+ border-radius: 0.5rem;
+ overflow: hidden;
+ box-shadow: 0 2px 8px oklch(0% 0 0 / 0.1);
+ transition: transform 0.2s, box-shadow 0.2s;
+
+ &:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 4px 16px oklch(0% 0 0 / 0.15);
+ }
+}
+
+.card-image img {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ object-fit: cover;
+}
+
+.card-content {
+ padding: 1.5rem;
+}
+
+.card-title {
+ margin: 0 0 0.5rem;
+ font-size: 1.25rem;
+
+ & a {
+ color: inherit;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+
+.card-date {
+ display: block;
+ font-size: 0.875rem;
+ color: var(--color-text-muted);
+ margin-bottom: 0.75rem;
+}
+
+.card-summary {
+ margin: 0 0 1rem;
+ line-height: 1.6;
+}
+
+.card-link {
+ font-weight: 500;
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+```
+
+### Timeline Layout
+
+**custom/templates/list-timeline.php:**
+
+```php
+= $pageContent ?>
+
+
+
+
+
= $year ?>
+
+
+
+
+
+ = $item['formatted_date'] ?? ($item['date'] ?? '') ?>
+
+
+
+
+
+
+
= htmlspecialchars($item['summary']) ?>
+
+
+
+
+
+```
+
+**CSS:**
+
+```css
+.timeline {
+ position: relative;
+ max-width: 800px;
+ margin: 2rem 0;
+ padding-left: 2rem;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 2px;
+ background: var(--color-border);
+ }
+}
+
+.timeline-year {
+ margin: 2rem 0 1rem;
+
+ & h2 {
+ font-size: 1.5rem;
+ color: var(--color-accent);
+ }
+}
+
+.timeline-item {
+ position: relative;
+ margin-bottom: 2rem;
+
+ &::before {
+ content: '';
+ position: absolute;
+ left: -2.5rem;
+ top: 0.5rem;
+ width: 0.75rem;
+ height: 0.75rem;
+ border-radius: 50%;
+ background: var(--color-accent);
+ border: 2px solid var(--color-bg);
+ }
+}
+
+.timeline-date {
+ display: block;
+ font-size: 0.875rem;
+ color: var(--color-text-muted);
+ margin-bottom: 0.25rem;
+}
+
+.timeline-content {
+ & h3 {
+ margin: 0 0 0.5rem;
+ font-size: 1.125rem;
+ }
+
+ & p {
+ margin: 0;
+ line-height: 1.6;
+ }
+}
+```
+
+### Magazine Layout
+
+**custom/templates/list-magazine.php:**
+
+```php
+= $pageContent ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = htmlspecialchars($featured['summary']) ?>
+
+
+
+
+ Read article →
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = $item['formatted_date'] ?? $item['date'] ?>
+
+
+
+
+
+
+
+
+```
+
+## Using Partials (Template Includes)
+
+Break complex templates into reusable components.
+
+### Creating a Partial
+
+**custom/templates/partials/post-card.php:**
+
+```php
+
+
+
+
+
+
+
+
+
+
+ = htmlspecialchars($post['summary']) ?>
+
+
+```
+
+### Using a Partial
+
+**custom/templates/list.php:**
+
+```php
+= $pageContent ?>
+
+
+
+
+
+
+```
+
+**Note:** Set `$post` before including, as the partial expects it.
+
+## Conditional Templates
+
+Use metadata to vary presentation.
+
+```php
+
+
+ = $content ?>
+
+
+
+
+ = $content ?>
+
+
+
+```
+
+**Set in metadata:**
+
+```ini
+[settings]
+layout = "wide"
+```
+
+## Template Best Practices
+
+### 1. Always Escape Output
+
+```php
+
+= htmlspecialchars($title) ?>
+
+
+= $title ?>
+```
+
+### 2. Check Before Using
+
+```php
+
+
+ By = htmlspecialchars($metadata['author']) ?>
+
+
+
+By = htmlspecialchars($metadata['author']) ?>
+```
+
+### 3. Use Semantic HTML
+
+```php
+
+
+
+ Content
+
+
+
+
+
+```
+
+### 4. Add ARIA Labels
+
+```php
+
+
+
+
+
+
+
+```
+
+### 5. Keep Logic Minimal
+
+```php
+
+
+ = $item['formatted_date'] ?>
+
+
+
+
+ strtotime($item['date']) > strtotime('-30 days')
+);
+usort($recentPosts, fn($a, $b) => strcmp($b['date'], $a['date']));
+?>
+```
+
+## What's Next?
+
+- **[Template Variables Reference](#)** — See all available variables
+- **[Plugin System](#)** — Add custom variables to templates
+- **[Styling Guide](#)** — Style your custom templates
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..4b3ed27
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,240 @@
+# FolderWeb Documentation
+
+Welcome to FolderWeb—a minimal PHP framework that turns your folder structure into a website. No build steps, no databases, no complexity. Just files, folders, and modern web standards.
+
+## What is FolderWeb?
+
+FolderWeb is a file-based content management system that:
+
+- **Uses your folder structure as URL structure** — Drop files in folders, get pages
+- **Works without JavaScript** — Pure PHP and modern CSS
+- **Requires no build process** — Edit, save, refresh. Done.
+- **Supports multiple languages** — Built-in i18n with URL-based language switching
+- **Stays maintainable** — Code designed to last years or decades
+
+Perfect for blogs, documentation sites, portfolios, and small business websites where simplicity and longevity matter.
+
+## Quick Navigation
+
+### New to FolderWeb?
+
+Start here to get up and running:
+
+1. **[Getting Started](01-getting-started/index.md)** — Installation and quick start guide
+2. **[Adding Content](02-tutorial/01-adding-content.md)** — Learn how to create pages
+3. **[Styling Your Site](02-tutorial/02-styling.md)** — Customize the design
+4. **[Working with Templates](02-tutorial/03-templates.md)** — Control presentation
+
+### Building a Site?
+
+Practical guides for common tasks:
+
+- **[Tutorial: Adding Content](02-tutorial/01-adding-content.md)** — Files, folders, metadata
+- **[Tutorial: Styling](02-tutorial/02-styling.md)** — Modern CSS techniques
+- **[Tutorial: Templates](02-tutorial/03-templates.md)** — Page and list layouts
+
+### Need a Reference?
+
+Look up specific features and options:
+
+- **[Configuration](03-reference/01-configuration.md)** — Config file options
+- **[Metadata](03-reference/02-metadata.md)** — Page and directory metadata
+- **[Template Variables](03-reference/03-template-variables.md)** — Available template variables
+- **[Internationalization](03-reference/04-internationalization.md)** — Multilingual setup
+
+### Extending FolderWeb?
+
+Advanced guides for developers:
+
+- **[Plugin System](04-development/01-plugin-system.md)** — Create plugins with hooks
+- **[Creating Templates](04-development/02-creating-templates.md)** — Advanced template creation
+
+## Core Concepts
+
+### File-Based Routing
+
+Your folder structure **is** your URL structure:
+
+```
+content/
+├── about.md → /about/
+├── blog/
+│ ├── index.md → /blog/
+│ └── first-post/
+│ └── index.md → /blog/first-post/
+└── contact.html → /contact/
+```
+
+No configuration, no route definitions—just create folders and files.
+
+### Content Types
+
+FolderWeb supports three content types:
+
+- **Markdown (`.md`)** — Write in Markdown, get HTML
+- **HTML (`.html`)** — Static HTML content
+- **PHP (`.php`)** — Dynamic content with PHP
+
+Mix and match as needed. All three can coexist in the same directory.
+
+### Template System
+
+Templates control how content is presented:
+
+- **Base template** — HTML scaffold (header, nav, footer)
+- **Page template** — Wraps single-page content
+- **List template** — Displays collections (blogs, portfolios)
+
+Override defaults by creating `custom/templates/`.
+
+### Metadata Files
+
+Configure pages with `metadata.ini` files:
+
+```ini
+title = "My Page"
+summary = "Short description"
+date = "2024-12-15"
+slug = "custom-url"
+menu = 1
+```
+
+Control titles, URLs, navigation, templates, and more.
+
+### Plugin System
+
+Extend functionality with a simple hook system:
+
+```php
+Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
+ $vars['customVariable'] = 'value';
+ return $vars;
+});
+```
+
+Three hooks provide extensibility without complexity.
+
+## Philosophy
+
+FolderWeb follows these principles:
+
+### Minimalism
+
+Use only what's strictly necessary. No frameworks, no build tools, no package managers for frontend code. If it doesn't provide clear, lasting value, it doesn't belong.
+
+### Longevity
+
+Code should be readable and maintainable for years or decades. Avoid rapidly changing components and dependencies. Favor web standards over abstractions.
+
+### Simplicity
+
+HTML, PHP, and CSS. That's it. No preprocessing, no transpiling, no complex build pipelines. Edit a file, refresh the browser, see the result.
+
+### Performance
+
+Fast page loads, minimal HTTP requests, no JavaScript parsing delay. Performance metrics displayed in the footer—transparency and pride.
+
+## Documentation Structure
+
+```
+docs/
+├── 01-getting-started/
+│ └── index.md # Installation and quick start
+├── 02-tutorial/
+│ ├── 01-adding-content.md # How to create content
+│ ├── 02-styling.md # Customizing styles
+│ └── 03-templates.md # Working with templates
+├── 03-reference/
+│ ├── 01-configuration.md # Config options
+│ ├── 02-metadata.md # Metadata fields
+│ ├── 03-template-variables.md # Available variables
+│ └── 04-internationalization.md # Multilingual setup
+└── 04-development/
+ ├── 01-plugin-system.md # Creating plugins
+ └── 02-creating-templates.md # Advanced templates
+```
+
+## Common Tasks
+
+### I want to...
+
+**...add a new page**
+→ Create a `.md`, `.html`, or `.php` file in `content/`
+→ See [Adding Content](02-tutorial/01-adding-content.md)
+
+**...change the design**
+→ Edit `custom/styles/base.css`
+→ See [Styling Your Site](02-tutorial/02-styling.md)
+
+**...customize the layout**
+→ Copy and edit templates in `custom/templates/`
+→ See [Working with Templates](02-tutorial/03-templates.md)
+
+**...add a blog**
+→ Create `content/blog/` with dated subfolders
+→ See [Adding Content](02-tutorial/01-adding-content.md)
+
+**...translate my site**
+→ Enable the languages plugin and create language files
+→ See [Internationalization](03-reference/04-internationalization.md)
+
+**...add custom functionality**
+→ Create a plugin in `custom/plugins/global/`
+→ See [Plugin System](04-development/01-plugin-system.md)
+
+**...add pages to the menu**
+→ Set `menu = 1` in `metadata.ini`
+→ See [Metadata Reference](03-reference/02-metadata.md)
+
+**...use a different list layout**
+→ Set `page_template = "list-grid"` in `metadata.ini`
+→ See [Template Variables](03-reference/03-template-variables.md)
+
+## Examples
+
+FolderWeb includes extensive examples in `app/default/content/examples/`:
+
+- **Markdown demo** — Full Markdown feature showcase
+- **Cover images** — How cover images work
+- **Metadata examples** — All metadata options demonstrated
+- **Template demos** — Grid and compact list layouts
+- **Multilingual** — Language support examples
+- **Mixed formats** — Combining Markdown, HTML, and PHP
+
+Browse the examples to see features in action.
+
+## Getting Help
+
+**Read the docs:**
+Start with [Getting Started](01-getting-started/index.md) and work through the tutorial.
+
+**Check examples:**
+Look in `app/default/content/examples/` for working code.
+
+**Review the code:**
+FolderWeb is intentionally simple. Read the source in `app/` to understand how it works.
+
+**File an issue:**
+Found a bug or have a question? Open an issue on GitHub.
+
+## Contributing
+
+FolderWeb is open source and welcomes contributions:
+
+- **Report bugs** — Open an issue with steps to reproduce
+- **Suggest features** — Propose improvements (but remember: minimalism is key)
+- **Share plugins** — Created a useful plugin? Share it with the community
+- **Improve docs** — Found something unclear? Submit a pull request
+
+## What's Next?
+
+Ready to build? Start with [Getting Started](01-getting-started/index.md) to install FolderWeb and create your first page.
+
+Or jump straight to:
+- **[Tutorial: Adding Content](02-tutorial/01-adding-content.md)** — Learn the basics
+- **[Configuration Reference](03-reference/01-configuration.md)** — Dive into details
+- **[Plugin System](04-development/01-plugin-system.md)** — Extend functionality
+
+---
+
+**FolderWeb** — Just enough web, nothing more.