Create a simple hero section with title, subtitle, and call-to-action

button Add a features section with icons and descriptions Include a
stats section with live data Add a responsive design with modern CSS
features
This commit is contained in:
Ruben 2025-11-27 21:31:47 +01:00
parent f0d50ff8bf
commit 0e19040473
101 changed files with 1356 additions and 7532 deletions

View file

@ -0,0 +1,50 @@
<?php
// Dynamisk hero-seksjon som viser PHP-muligheter
$features = [
['icon' => '📁', 'title' => 'Filbasert', 'description' => 'Mapper blir URL-er automatisk', 'url' => '/examples/file-based-routing/'],
['icon' => '⚡', 'title' => 'Uten byggesteg', 'description' => 'Rediger og oppdater—det er alt', 'url' => '/examples/no-build-step/'],
['icon' => '🎨', 'title' => 'Bland formater', 'description' => 'Kombiner .md, .html og .php', 'url' => '/examples/mix-formats/'],
['icon' => '🌍', 'title' => 'Flerspråklig', 'description' => 'Innebygd i18n-støtte', 'url' => '/multilingual/'],
['icon' => '📝', 'title' => 'Markdown', 'description' => 'Skriv innhold i markdown', 'url' => '/examples/2024-12-15-markdown-demo/'],
['icon' => '🎭', 'title' => 'Maler', 'description' => 'Egendefinerte layout og stiler', 'url' => '/examples/templates-demo/'],
];
$stats = [
'files' => count(glob(__DIR__ . '/**/*', GLOB_BRACE)) ?: 0,
'generated' => number_format((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2) . 'ms',
'php_version' => PHP_VERSION,
];
?>
<section class="hero">
<h1 class="hero-title">FolderWeb</h1>
<p class="hero-subtitle">
Slipp filer i mapper. De rendres umiddelbart.
Ingen konfigurasjon, ingen byggesteg, ingen kompleksitet.
</p>
<div class="features">
<?php foreach ($features as $feature): ?>
<a href="<?= htmlspecialchars($feature['url']) ?>" class="feature-card">
<span class="feature-icon"><?= $feature['icon'] ?></span>
<h3 class="feature-title"><?= htmlspecialchars($feature['title']) ?></h3>
<p class="feature-description"><?= htmlspecialchars($feature['description']) ?></p>
</a>
<?php endforeach; ?>
</div>
<div class="stats">
<div class="stat">
<span class="stat-value"><?= $stats['files'] ?></span>
<span class="stat-label">Filer i innhold</span>
</div>
<div class="stat">
<span class="stat-value"><?= $stats['generated'] ?></span>
<span class="stat-label">Side generert</span>
</div>
<div class="stat">
<span class="stat-value">PHP <?= $stats['php_version'] ?></span>
<span class="stat-label">Runtime</span>
</div>
</div>
</section>

View file

@ -0,0 +1,50 @@
<?php
// Dynamic hero section showcasing PHP capabilities
$features = [
['icon' => '📁', 'title' => 'File-Based', 'description' => 'Folders become URLs automatically', 'url' => '/examples/file-based-routing/'],
['icon' => '⚡', 'title' => 'No Build Step', 'description' => 'Edit and refresh—that\'s it', 'url' => '/examples/no-build-step/'],
['icon' => '🎨', 'title' => 'Mix Formats', 'description' => 'Combine .md, .html, and .php', 'url' => '/examples/mix-formats/'],
['icon' => '🌍', 'title' => 'Multilingual', 'description' => 'Built-in i18n support', 'url' => '/multilingual/'],
['icon' => '📝', 'title' => 'Markdown', 'description' => 'Write content in markdown', 'url' => '/examples/2024-12-15-markdown-demo/'],
['icon' => '🎭', 'title' => 'Templates', 'description' => 'Custom layouts and styles', 'url' => '/examples/templates-demo/'],
];
$stats = [
'files' => count(glob(__DIR__ . '/**/*', GLOB_BRACE)) ?: 0,
'generated' => number_format((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2) . 'ms',
'php_version' => PHP_VERSION,
];
?>
<section class="hero">
<h1 class="hero-title">FolderWeb</h1>
<p class="hero-subtitle">
Drop files in folders. They render immediately.
No configuration, no build step, no complexity.
</p>
<div class="features">
<?php foreach ($features as $feature): ?>
<a href="<?= htmlspecialchars($feature['url']) ?>" class="feature-card">
<span class="feature-icon"><?= $feature['icon'] ?></span>
<h3 class="feature-title"><?= htmlspecialchars($feature['title']) ?></h3>
<p class="feature-description"><?= htmlspecialchars($feature['description']) ?></p>
</a>
<?php endforeach; ?>
</div>
<div class="stats">
<div class="stat">
<span class="stat-value"><?= $stats['files'] ?></span>
<span class="stat-label">Files in content</span>
</div>
<div class="stat">
<span class="stat-value"><?= $stats['generated'] ?></span>
<span class="stat-label">Page generated</span>
</div>
<div class="stat">
<span class="stat-value">PHP <?= $stats['php_version'] ?></span>
<span class="stat-label">Runtime</span>
</div>
</div>
</section>

View file

@ -1,6 +0,0 @@
<header style="text-align: center; padding: 4rem 0 2rem;">
<h1 style="font-size: clamp(2.5rem, 5vw, 4rem); margin-bottom: 1rem;">Welcome to FolderWeb</h1>
<p style="font-size: clamp(1.125rem, 2vw, 1.5rem); color: oklch(0.5 0 0); max-width: 60ch; margin: 0 auto;">
A minimalist PHP framework that turns folders into websites. No JavaScript, no build tools, just simple files.
</p>
</header>

View file

@ -1,7 +0,0 @@
## Getting Started
This is demo content to help you understand how FolderWeb works. To replace it with your own content:
1. Create a `/content` folder in your project root
2. Add your content files (`.md`, `.html`, or `.php`)
3. This demo will automatically disappear

View file

@ -1,15 +0,0 @@
## Core Concepts
### File-Based Routing
Drop a file in a folder and it's instantly accessible at a URL matching that path. Your folder structure becomes your URL structure.
### Multiple Content Types
- **Markdown** - Write in `.md` files, automatically converted to HTML
- **HTML** - Pure HTML files for complete control
- **PHP** - Dynamic content when you need it
### Multi-File Pages
Any folder without subfolders renders all content files (`.md`, `.html`, `.php`) in alphanumerical order. Mix formats freely!

View file

@ -0,0 +1,45 @@
<!-- The hero.php file renders above this markdown content -->
<!-- Files are combined in alphabetical order: hero.php, then index.md -->
## Quick Start
1. **Empty the content folder** Delete everything in `/content/` to start fresh
2. **Copy the default folder** Copy `/app/default/` to `/custom/` (one level up from app/) to create your own theme
3. **Add your content** Drop `.md`, `.html`, or `.php` files in `/content/` and they're live
4. **Customize templates** Edit files in `/custom/templates/` to change the look
5. **Style it** Add CSS in `/custom/styles/styles.css` for global styles
## What Makes FolderWeb Different
**File-based routing** Your folder structure is your URL structure. No configuration needed.
**No JavaScript** Fast, simple, accessible websites that work everywhere.
**Modern PHP & CSS** Clean code using the latest language features and CSS capabilities.
**No build step** Edit a file, refresh the page. That's it.
**Decade-proof** Minimal dependencies mean this will work for years without breaking.
## How This Page Works
This frontpage demonstrates how you can **mix different file types** to create dynamic, custom layouts:
- **`hero.php`** Dynamic PHP component with live stats and styling
- **`index.md`** Static markdown content (this section!)
Files in the same directory are rendered alphabetically. Want a different order? Prefix filenames with numbers like `00-hero.php` and `01-content.md`.
## Features Demonstrated Here {#features}
Explore this demo site to see what FolderWeb can do:
- [Examples](/examples/) Markdown, metadata, dates, and cover images
- [Multilingual](/multilingual/) Build sites in multiple languages
- [Nested Content](/examples/nested/) Organize content as deep as you need
## Philosophy
**Just enough, nothing more.** This framework uses minimal PHP to enable modern conveniences while remaining maintainable for years or decades.
Read more in `CLAUDE.md` to understand the principles behind FolderWeb.

View file

@ -0,0 +1,45 @@
<!-- hero.no.php-filen rendres over dette markdown-innholdet -->
<!-- Filer kombineres i alfabetisk rekkefølge: hero.no.php, deretter index.no.md -->
## Hurtigstart
1. **Tøm innholdsmappen** Slett alt i `/content/` for å starte fra bunnen av
2. **Kopier standardmappen** Kopier `/app/default/` til `/custom/` (ett nivå opp fra app/) for å lage ditt eget tema
3. **Legg til innhold** Slipp `.md`, `.html`, eller `.php`-filer i `/content/` og de er live
4. **Tilpass maler** Rediger filer i `/custom/templates/` for å endre utseendet
5. **Stil det** Legg til CSS i `/custom/styles/styles.css` for globale stiler
## Hva gjør FolderWeb annerledes
**Filbasert routing** Mappestrukturen din er URL-strukturen din. Ingen konfigurasjon nødvendig.
**Ingen JavaScript** Raske, enkle, tilgjengelige nettsteder som fungerer overalt.
**Moderne PHP & CSS** Ren kode som bruker de nyeste språkfunksjonene og CSS-mulighetene.
**Uten byggesteg** Rediger en fil, oppdater siden. Det er det.
**Tiår-sikker** Minimale avhengigheter betyr at dette vil fungere i årevis uten å gå i stykker.
## Hvordan denne siden fungerer
Denne forsiden demonstrerer hvordan du kan **blande forskjellige filtyper** for å lage dynamiske, egendefinerte layout:
- **`hero.no.php`** Dynamisk PHP-komponent med live statistikk og styling
- **`index.no.md`** Statisk markdown-innhold (denne seksjonen!)
Filer i samme katalog rendres alfabetisk. Vil du ha en annen rekkefølge? Prefikser filnavn med tall som `00-hero.php` og `01-innhold.md`.
## Funksjoner demonstrert her {#funksjoner}
Utforsk denne demosiden for å se hva FolderWeb kan gjøre:
- [Eksempler](/examples/) Markdown, metadata, datoer og forsidebilder
- [Flerspråklig](/multilingual/) Bygg nettsteder på flere språk
- [Nestet innhold](/examples/nested/) Organiser innhold så dypt du trenger
## Filosofi
**Akkurat nok, ikke mer.** Dette rammeverket bruker minimal PHP for å muliggjøre moderne bekvemmeligheter samtidig som det forblir vedlikeholdbart i årevis eller tiår.
Les mer i `CLAUDE.md` for å forstå prinsippene bak FolderWeb.

View file

@ -1,13 +0,0 @@
<article>
<h2>Smart Features</h2>
<ul>
<li><strong>Metadata</strong> - Use <code>metadata.ini</code> files for titles, dates, summaries</li>
<li><strong>Date extraction</strong> - Folder names like <code>2025-11-01-title</code> automatically show dates</li>
<li><strong>Cover images</strong> - Add <code>cover.jpg</code> for list view thumbnails</li>
<li><strong>Templates</strong> - Custom templates in <code>/custom/templates/</code> override defaults</li>
</ul>
<h2>Explore the Demo</h2>
<p>Check out the <a href="/articles/">Articles</a> and <a href="/about/">About</a> pages to see different content types in action.</p>
</article>

View file

@ -1,11 +0,0 @@
## About This Frontpage
**This frontpage demonstrates the multi-file approach!** It's composed of:
1. `00-welcome.php` - Hero header (PHP/HTML)
2. `00a-getting-started.md` - Getting started guide (Markdown)
3. `01-core-concepts.md` - Core concepts (Markdown)
4. `02-features.html` - Features and links (HTML)
5. `03-this-page-demo.md` - This explanation (Markdown)
All files in the root `/content` folder are rendered together, just like pages in subfolders. Name your files anything you want—they'll render in alphanumerical order.

View file

@ -1,15 +0,0 @@
# About FolderWeb
FolderWeb is a minimalist PHP framework designed for simplicity, longevity, and maintainability. It's built on a simple philosophy: **just enough, nothing more**.
## Philosophy
Modern web development has become unnecessarily complex. Build tools, package managers, JavaScript frameworks that change every few months—it's exhausting and unsustainable.
FolderWeb is different. It's built to:
- **Work for decades** without requiring constant updates
- **Be understandable** by reading a few hundred lines of code
- **Stay maintainable** without specialized knowledge
- **Load fast** with no JavaScript overhead
- **Just work** without configuration or setup

View file

@ -1,15 +0,0 @@
<article>
<h2>Design Principles</h2>
<h3>Minimalism</h3>
<p>Use only what is strictly necessary. No frameworks, no build tools, no package managers for frontend code. Every line of code must justify its existence.</p>
<h3>File-Based Everything</h3>
<p>Your folder structure is your URL structure. Drop a file in a folder and it's instantly accessible. No routes to configure, no databases to set up.</p>
<h3>Override, Never Modify</h3>
<p>Custom templates and styles go in <code>/custom/</code> and automatically override defaults. The core files in <code>/app/default/</code> remain untouched and updateable.</p>
<h3>Modern Standards</h3>
<p>Use modern PHP 8.4+ features (property hooks, readonly classes, modern array functions) and modern CSS capabilities. Avoid JavaScript entirely—it's not needed for content-focused sites.</p>
</article>

View file

@ -1,21 +0,0 @@
<article>
<h2>Technology Stack</h2>
<h3>Backend</h3>
<ul>
<li><strong>PHP 8.4+</strong> - Modern PHP with property hooks, readonly classes, array_find(), and type safety</li>
<li><strong>Apache</strong> - With mod_rewrite for clean URLs</li>
<li><strong>Parsedown</strong> - Simple, reliable Markdown parser</li>
</ul>
<h3>Frontend</h3>
<ul>
<li><strong>HTML5</strong> - Semantic markup following best practices</li>
<li><strong>CSS3</strong> - Modern features like Grid, clamp(), OKLCH colors, CSS nesting</li>
<li><strong>No JavaScript</strong> - By design, for faster loads and simpler maintenance</li>
</ul>
<aside style="background: #f5f5f5; padding: 1rem; border-left: 4px solid #333; margin: 2rem 0;">
<p><strong>Note:</strong> This section demonstrates using PHP for dynamic content. The current date is: <strong><?= date('F j, Y') ?></strong></p>
</aside>
</article>

View file

@ -1,19 +0,0 @@
## What It's Not
FolderWeb is **not**:
- A CMS with an admin panel
- A single-page application framework
- A solution for complex web applications
- Trying to scale to millions of users
- Following current trends and fads
## What It Is
FolderWeb **is**:
- A simple way to publish content
- A foundation that will work for decades
- A teaching tool for web fundamentals
- A protest against unnecessary complexity
- Perfect for documentation, blogs, portfolios, small business sites

View file

@ -1,21 +0,0 @@
## Get Started
Ready to build something simple and lasting?
1. Create a `/content` folder
2. Add your first `.md` file
3. That's it—you're publishing
No build step. No npm install. No configuration files. Just content.
---
**This page demonstrates FolderWeb's multi-file approach.** Notice how this page is built from multiple files that render in alphanumerical order:
- `00-introduction.md` - Markdown content
- `01-design-principles.html` - Static HTML
- `02-technology-stack.php` - Dynamic PHP (shows current date)
- `03-what-it-is-not.md` - More Markdown
- `04-get-started.md` - This section
Mix file types freely. They all render together seamlessly!

View file

@ -1,3 +0,0 @@
title = "About FolderWeb"
menu = true
menu_order = 2

View file

@ -1,19 +0,0 @@
/* Page-specific styles for About page */
article {
border-left: 4px solid oklch(0.65 0.15 250);
padding-left: 1.5rem;
}
article h2 {
color: oklch(0.50 0.12 250);
font-size: 1.8rem;
}
aside {
background-color: oklch(0.95 0.02 250);
padding: 1.5rem;
border-radius: 0.5rem;
margin-top: 2rem;
border: 2px solid oklch(0.85 0.05 250);
}

View file

@ -1,206 +0,0 @@
# Markdown Guide
Markdown is a lightweight markup language that's easy to write and read. FolderWeb uses [Parsedown](https://parsedown.org/) to convert your Markdown files into beautiful HTML.
## Headings
Use `#` symbols for headings:
```markdown
# Heading 1
## Heading 2
### Heading 3
#### Heading 4
##### Heading 5
###### Heading 6
```
## Emphasis
Make text **bold** or *italic*:
```markdown
*italic text* or _italic text_
**bold text** or __bold text__
***bold and italic*** or ___bold and italic___
```
## Lists
### Unordered Lists
```markdown
- Item one
- Item two
- Item three
- Nested item
- Another nested item
```
### Ordered Lists
```markdown
1. First item
2. Second item
3. Third item
1. Nested item
2. Another nested item
```
## Links
```markdown
[Link text](https://example.com)
[Link with title](https://example.com "Title text")
```
Example: [Visit FolderWeb](#)
## Images
```markdown
![Alt text](image.jpg)
![Alt text with title](image.jpg "Image title")
```
## Code
### Inline Code
Use backticks for `inline code`:
```markdown
Use the `$variable` in your code
```
### Code Blocks
Use triple backticks for code blocks:
````markdown
```php
<?php
echo "Hello, World!";
?>
```
````
Renders as:
```php
<?php
echo "Hello, World!";
?>
```
## Blockquotes
```markdown
> This is a blockquote.
> It can span multiple lines.
>
> And multiple paragraphs.
```
Result:
> This is a blockquote.
> It can span multiple lines.
## Horizontal Rules
Create a horizontal rule with three or more hyphens, asterisks, or underscores:
```markdown
---
***
___
```
---
## Tables
```markdown
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
```
Result:
| Header 1 | Header 2 | Header 3 |
|----------|----------|----------|
| Cell 1 | Cell 2 | Cell 3 |
| Cell 4 | Cell 5 | Cell 6 |
## Best Practices
### Use Semantic Structure
Start with `# H1` for your page title, then use `## H2`, `### H3`, etc. for sections.
### Write Readable Markdown
```markdown
# Good Example
This paragraph is easy to read with proper spacing.
## Section Heading
- List items are clear
- Each on its own line
---
# Bad Example
No spacing makes it hard to read.
##SectionWithoutSpace
-ListItemsSmashed-Together
```
### Links in FolderWeb
Internal links work best with absolute paths:
```markdown
[About page](/about/)
[Articles](/articles/)
[Specific article](/articles/2025-10-15-markdown-guide/)
```
## Advanced Features
### HTML in Markdown
You can use HTML directly in Markdown when needed:
```markdown
<div class="custom-class">
Custom HTML content
</div>
```
### Escaping Characters
Use backslash to escape Markdown characters:
```markdown
\*This text is not italic\*
\[This is not a link\]
```
## Tips for FolderWeb
1. **Use descriptive filenames** - `article.md` is better than `content.md`
2. **Add metadata** - Use `metadata.ini` for titles, dates, and summaries
3. **Include images** - Place images in the same directory as your content
4. **Add cover images** - Use `cover.jpg` for list view thumbnails
## Further Reading
- [Markdown Guide](https://www.markdownguide.org/) - Comprehensive Markdown reference
- [Parsedown Documentation](https://parsedown.org/) - The parser FolderWeb uses
- [CommonMark Spec](https://commonmark.org/) - Markdown specification

View file

@ -1,3 +0,0 @@
title = "Markdown Guide"
date = "2025-10-15"
summary = "Master Markdown syntax for beautiful, semantic content in FolderWeb."

View file

@ -1,165 +0,0 @@
# Templates and Customization
FolderWeb is built on a simple principle: **never modify defaults, always override**. This guide shows you how to customize your site while keeping it maintainable.
## The Override System
FolderWeb checks for custom files before falling back to defaults:
1. Check `/custom/templates/` → Use custom template
2. Fall back to `/app/default/templates/` → Use default template
This means you can override any part of the system without touching the core files.
## Available Templates
### Base Template
The main HTML structure with header, navigation, and footer.
**Override**: `/custom/templates/base.php`
### Page Template
Wraps single pages and articles.
**Override**: `/custom/templates/page.php`
### List Templates
FolderWeb includes multiple list view variants:
- `list.php` - Simple list
- `list-grid.php` - Grid layout
- `list-card-grid.php` - Card grid with images
- `list-faq.php` - Expandable FAQ format
**Select via metadata**:
```ini
page_template = "list-card-grid"
```
## Customizing Styles
### Add Your Own CSS
Create `/custom/styles/base.css` and it automatically overrides default styles.
Example custom stylesheet:
```css
:root {
--color-primary: oklch(0.5 0.2 270);
--color-background: oklch(0.98 0 0);
--font-body: 'Georgia', serif;
}
article h1 {
color: var(--color-primary);
font-size: clamp(2rem, 5vw, 3rem);
}
```
### Modern CSS Features
FolderWeb's default styles use modern CSS:
- **CSS Nesting** - Scope styles naturally
- **OKLCH Colors** - Perceptually uniform colors
- **CSS Grid** - Flexible layouts
- **Clamp()** - Responsive sizing
- **Logical Properties** - Better internationalization
## Custom Fonts
1. Place font files in `/custom/fonts/`
2. Reference them in your custom CSS:
```css
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
}
body {
font-family: 'MyFont', sans-serif;
}
```
## Metadata Options
Control content behavior with `metadata.ini` files:
```ini
; Basic fields
title = "Page Title"
date = "2025-10-28"
summary = "Short description"
; Navigation
menu = true
menu_order = 1
; Templates
page_template = "list-card-grid"
; Redirects
redirect = "https://example.com"
; Attachments
pdf = "document.pdf"
```
## Template Variables
Templates have access to specific variables:
### Base Template
- `$content` - Page content
- `$currentLang` - Current language code
- `$navigation` - Navigation items array
- `$pageTitle` - Page title
### Page Template
- `$content` - Article content
- `$pageMetadata` - Metadata array
- `$translations` - Translation strings
### List Templates
- `$items` - Array of subitems
- `$metadata` - Directory metadata
- `$pageContent` - Intro text
- `$translations` - Translation strings
## Creating a Custom Template
Example custom page template:
```php
<?php
// /custom/templates/page.php
?>
<article class="custom-layout">
<?php if (isset($pageMetadata['date'])): ?>
<time><?= htmlspecialchars($pageMetadata['date']) ?></time>
<?php endif; ?>
<?= $content ?>
<footer>
<p>Custom footer content</p>
</footer>
</article>
```
## Best Practices
1. **Never modify `/app/default/`** - Always create overrides in `/custom/`
2. **Use metadata** - Keep configuration in `metadata.ini` files
3. **Leverage CSS variables** - Easy theming without rewriting styles
4. **Keep it simple** - The less custom code, the easier to maintain
## Next Steps
- Explore default templates in `/app/default/templates/`
- Study the default CSS in `/app/default/styles/base.css`
- Check out the [Markdown Guide](/articles/2025-10-15-markdown-guide/) for content formatting

View file

@ -1,3 +0,0 @@
title = "Templates and Customization"
date = "2025-10-28"
summary = "Customize your FolderWeb site with templates, styles, and metadata options."

View file

@ -1,79 +0,0 @@
# Getting Started with FolderWeb
FolderWeb is designed to be the simplest way to publish content on the web. This guide will walk you through the core concepts and get you publishing in minutes.
## Installation
FolderWeb requires PHP 8.4+ and Apache with `mod_rewrite` enabled.
### Using Docker (Recommended for Development)
```bash
cd development
docker compose up
```
Visit `http://localhost:8080` to see your site.
### Manual Installation
1. Point Apache's document root to the `/content` directory
2. Ensure the `/app` directory is accessible at the same level
3. Enable `mod_rewrite` in Apache
4. That's it!
## Creating Your First Page
The easiest way to understand FolderWeb is to create some content.
### Create a Simple Page
1. Create a new directory: `/content/hello/`
2. Add a file: `/content/hello/page.md`
3. Write some Markdown:
```markdown
# Hello World
This is my first page in FolderWeb!
```
Your page is now live at `/hello/`
### Create an Article with Metadata
For richer content, add metadata:
1. Create: `/content/articles/2025-11-01-my-article/`
2. Add metadata: `/content/articles/2025-11-01-my-article/metadata.ini`
```ini
title = "My First Article"
date = "2025-11-01"
summary = "A brief description of my article"
```
3. Add content: `/content/articles/2025-11-01-my-article/article.md`
The date in the folder name is automatically extracted and displayed.
## File Types
FolderWeb supports three content types:
- **Markdown (`.md`)** - Write in Markdown, rendered as HTML
- **HTML (`.html`)** - Pure HTML for complete control
- **PHP (`.php`)** - Dynamic content when needed
## File Naming Conventions
- **Page content**: `page.md`, `page.html`, `page.php`
- **Articles/posts**: `article.md`, `post.md`, `single.md` (and `.html`/`.php` variants)
- **Frontpage**: `/content/frontpage.php`
- **Index override**: `index.php` in any directory takes precedence
## Next Steps
- Read the [Templates Guide](/articles/2025-10-28-templates-and-customization/) to customize your site
- Learn [Markdown syntax](/articles/2025-10-15-markdown-guide/) for better content
- Explore the default templates in `/app/default/templates/`

View file

@ -1,3 +0,0 @@
title = "Getting Started with FolderWeb"
date = "2025-11-01"
summary = "Learn the basics of FolderWeb and create your first content in minutes."

View file

@ -1,14 +0,0 @@
# Multi-File Content Pages
One of FolderWeb's most powerful features is the ability to compose a single page from multiple content files. This gives you flexibility in how you organize and author your content.
## How It Works
When a folder contains **no subdirectories**, FolderWeb treats it as a **page-type folder**. All `.md`, `.html`, and `.php` files in that folder are rendered in **alphanumerical order**.
This means you can:
- Break long content into manageable sections
- Mix file formats freely (Markdown, HTML, PHP)
- Reorder sections by renaming files
- Include dynamic PHP content alongside static content

View file

@ -1,24 +0,0 @@
## File Naming Examples
Here are some example naming patterns:
```
/my-page/
00-introduction.md
01-getting-started.md
02-advanced-topics.html
03-conclusion.php
```
Files render in this order:
1. `00-introduction.md`
2. `01-getting-started.md`
3. `02-advanced-topics.html`
4. `03-conclusion.php`
## Folder Types
FolderWeb automatically determines folder type:
- **Page-type folder**: No subdirectories → Renders all content files as a single page
- **Article-type folder**: Has subdirectories → Shows list view with links to subdirectories

View file

@ -1,15 +0,0 @@
<article>
<h2>Use Cases</h2>
<h3>Long Documentation</h3>
<p>Break lengthy documentation into logical sections. Each section gets its own file, making editing and maintenance easier.</p>
<h3>Mixed Content Types</h3>
<p>Use Markdown for simple text, HTML for complex layouts, and PHP for dynamic content—all on the same page.</p>
<h3>Collaborative Editing</h3>
<p>Multiple authors can work on different sections simultaneously without merge conflicts.</p>
<h3>Progressive Enhancement</h3>
<p>Start with simple Markdown files. Later, enhance specific sections with HTML or PHP without restructuring.</p>
</article>

View file

@ -1,16 +0,0 @@
<article>
<h2>Dynamic Content Example</h2>
<p>This section is a PHP file that generates dynamic content. Here are some examples:</p>
<div style="background: #f0f9ff; border: 1px solid #0284c7; padding: 1.5rem; border-radius: 8px; margin: 1.5rem 0;">
<h3>Server Information</h3>
<ul style="list-style: none; padding: 0;">
<li><strong>Current Time:</strong> <?= date('H:i:s') ?></li>
<li><strong>Today's Date:</strong> <?= date('l, F j, Y') ?></li>
<li><strong>PHP Version:</strong> <?= PHP_VERSION ?></li>
</ul>
</div>
<p>PHP files can access all the same variables and functions available throughout FolderWeb, making it easy to create dynamic, data-driven content.</p>
</article>

View file

@ -1,23 +0,0 @@
## Best Practices
### Use Descriptive Prefixes
Number your files with two-digit prefixes (`00-`, `01-`, `02-`) to maintain clear ordering:
- Allows up to 100 sections before needing three digits
- Keeps files sorted in file managers
- Makes reordering easy (just rename)
### Choose the Right Format
- **Markdown (`.md`)** - For most content. Simple, clean, readable.
- **HTML (`.html`)** - For complex layouts or embedded media.
- **PHP (`.php`)** - For dynamic content, calculations, or data display.
### Keep It Simple
Don't overcomplicate. If your page works well as a single file, keep it that way. Use multiple files when they genuinely make maintenance easier.
---
**This article itself demonstrates the multi-file approach.** View the source folder to see how it's structured!

View file

@ -1,3 +0,0 @@
title = "Multi-File Content Pages"
date = "2025-11-02"
summary = "Learn how to create pages from multiple content files in any format"

View file

@ -1,4 +0,0 @@
title = "Articles"
menu = true
menu_order = 1
page_template = "list"

View file

@ -1,3 +0,0 @@
# Articles
A collection of guides and tutorials to help you get the most out of FolderWeb.

View file

@ -1,22 +0,0 @@
/* Page-specific styles for Articles list */
.list-item {
border: 2px solid oklch(0.85 0.05 250);
transition: border-color 0.3s ease;
}
.list-item:hover {
border-color: oklch(0.65 0.15 250);
}
.list-item h2 a {
color: oklch(0.50 0.12 250);
}
.list-item .date {
background-color: oklch(0.95 0.02 250);
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.85rem;
display: inline-block;
}

View file

@ -0,0 +1,5 @@
<svg width="800" height="400" xmlns="http://www.w3.org/2000/svg">
<rect width="800" height="400" fill="#f0f0f0"/>
<circle cx="400" cy="200" r="80" fill="#6366f1" opacity="0.8"/>
<text x="400" y="340" font-family="system-ui, sans-serif" font-size="24" fill="#333" text-anchor="middle">Cover Image Example</text>
</svg>

After

Width:  |  Height:  |  Size: 324 B

View file

@ -0,0 +1,39 @@
# Cover Images and Assets
FolderWeb automatically detects and uses cover images for social sharing and list views.
## How Cover Images Work
Place an image named `cover.jpg`, `cover.png`, or `cover.webp` in your content folder and it's automatically:
- Used as the Open Graph image for social media
- Displayed in list views (when using templates that support it)
- Available at the same URL as your content
## Supported Formats
- `cover.jpg` or `cover.jpeg`
- `cover.png`
- `cover.webp`
- `cover.gif`
The system checks for these in order and uses the first one found.
## Other Assets
Any file in your content folder is accessible at the same URL path:
- Images: `photo.jpg`, `diagram.png`
- Documents: `download.pdf`, `report.docx`
- Data: `data.json`, `spreadsheet.csv`
Just reference them in your markdown:
```markdown
![Diagram](diagram.png)
[Download PDF](report.pdf)
```
## This Folder
This folder includes a simple SVG as the cover image. Check the folder structure to see how it's organized.

View file

@ -0,0 +1,2 @@
title = "Cover Images and Assets"
summary = "Learn how cover images and static assets work in FolderWeb"

View file

@ -0,0 +1,53 @@
# Working with Metadata
Metadata provides structured information about your pages and controls how they display.
## What is metadata.ini?
Every folder can have a `metadata.ini` file that defines:
- Title (overrides automatic title extraction)
- Date (overrides folder-based date detection)
- Summary (appears in list views)
- Custom template selection
- Other custom fields
## Example metadata.ini
```ini
title = "My Custom Title"
date = "2024-11-20"
summary = "A brief description that appears in lists"
[settings]
page_template = "list-grid"
show_date = false
```
## Common Use Cases
**Override automatic titles** If you want a title different from the folder name.
**Set explicit dates** When the folder name doesn't include a date.
**Add summaries** Give context in list views without showing full content.
**Choose templates** Different sections can use different list templates.
**Hide metadata** Set `show_date = false` to hide dates in lists.
## Custom Fields
You can add any fields you want:
```ini
author = "Jane Doe"
category = "Tutorial"
tags = "metadata, configuration, tutorial"
```
Then access them in custom templates via `$metadata['author']`.
## This Page's Metadata
Check out `metadata.ini` in this folder to see how the title, date, and summary are defined.

View file

@ -0,0 +1,3 @@
title = "Working with Metadata"
date = "2024-11-20"
summary = "Learn how to use metadata.ini files to control page information and behavior"

View file

@ -0,0 +1,74 @@
# Markdown Demonstration
This page shows the full range of Markdown formatting available through Parsedown.
## Text Formatting
You can use **bold text**, *italic text*, and even ***bold italic***.
Use `inline code` for technical terms or file paths like `/app/default/templates/`.
## Lists
Unordered lists:
- First item
- Second item
- Nested item
- Another nested item
- Third item
Ordered lists:
1. Step one
2. Step two
3. Step three
## Links and Images
Here's a [link to the homepage](/).
Images work too (when you add them to the folder):
![Example image](placeholder.jpg)
## Code Blocks
```php
<?php
function greet(string $name): string {
return "Hello, {$name}!";
}
echo greet('World');
```
## Blockquotes
> This is a blockquote. Perfect for highlighting important information or quotes from other sources.
>
> It can span multiple paragraphs.
## Tables
| Feature | Status |
|---------|--------|
| Markdown | ✓ |
| HTML | ✓ |
| PHP | ✓ |
| JavaScript | ✗ |
## Horizontal Rules
---
That line above is a horizontal rule, useful for separating sections.
## What You Can Do
- Write content in simple Markdown
- Mix HTML when needed
- Include images stored alongside your content
- Use all standard Markdown features
The file for this page is just `index.md` in a dated folder. The date is automatically extracted and displayed.

View file

@ -0,0 +1,74 @@
# Markdown-demonstrasjon
Denne siden viser hele utvalget av Markdown-formatering tilgjengelig gjennom Parsedown.
## Tekstformatering
Du kan bruke **fet tekst**, *kursiv tekst*, og til og med ***fet kursiv***.
Bruk `inline kode` for tekniske termer eller filstier som `/app/default/templates/`.
## Lister
Uspesifiserte lister:
- Første element
- Andre element
- Nestet element
- Enda et nestet element
- Tredje element
Ordnede lister:
1. Steg én
2. Steg to
3. Steg tre
## Lenker og bilder
Her er en [lenke til forsiden](/).
Bilder fungerer også (når du legger dem til i mappen):
![Eksempelbilde](placeholder.jpg)
## Kodeblokker
```php
<?php
function hils(string $navn): string {
return "Hei, {$navn}!";
}
echo hils('Verden');
```
## Blokksitater
> Dette er et blokksitat. Perfekt for å fremheve viktig informasjon eller sitater fra andre kilder.
>
> Det kan spenne over flere avsnitt.
## Tabeller
| Funksjon | Status |
|---------|--------|
| Markdown | ✓ |
| HTML | ✓ |
| PHP | ✓ |
| JavaScript | ✗ |
## Horisontale linjer
---
Linjen over er en horisontal linje, nyttig for å skille seksjoner.
## Hva du kan gjøre
- Skriv innhold i enkel Markdown
- Bland inn HTML når det trengs
- Inkluder bilder lagret sammen med innholdet ditt
- Bruk alle standard Markdown-funksjoner
Filen for denne siden er bare `index.no.md` i en datert mappe. Datoen blir automatisk hentet ut og vist.

View file

@ -0,0 +1,2 @@
title = "Markdown Demonstration"
summary = "Shows all the Markdown formatting features available through Parsedown"

View file

@ -0,0 +1,52 @@
# File-Based Routing
FolderWeb's routing is beautifully simple: **your folder structure is your URL structure**. No configuration files, no route definitions, no magic strings.
## How It Works
When you visit a URL, FolderWeb looks for matching folders and files:
```
/content/
├── index.md → /
├── about/
│ └── index.md → /about/
└── blog/
├── 2024-11-01-post/
│ └── index.md → /blog/post/
└── index.md → /blog/
```
## Automatic Features
**Folder names become URLs** Create a folder called `projects` and it's instantly available at `/projects/`
**Date prefixes are stripped** `2024-11-01-my-post` becomes `/my-post/` in the URL
**Custom slugs via metadata** Override the default URL with `slug = "custom-url"` in `metadata.ini`
**Trailing slashes** Directories always redirect to include trailing slashes for consistency
## Example
This very page demonstrates file-based routing! The path is:
```
app/default/content/examples/file-based-routing/index.md
```
Which renders at:
```
/examples/file-based-routing/
```
No routes to define. No configuration to update. Just files and folders.
## Benefits
- **Intuitive** If you can navigate folders, you understand the routing
- **Refactor-friendly** Moving content means moving folders
- **No broken links** URLs match the filesystem
- **Fast** No route matching overhead, direct file lookup
- **Predictable** What you see is what you get

View file

@ -0,0 +1,52 @@
# Filbasert routing
FolderWebs routing er vakkert enkelt: **mappestrukturen din er URL-strukturen din**. Ingen konfigurasjonsfiler, ingen rutedefinisjon, ingen magiske strenger.
## Hvordan det fungerer
Når du besøker en URL, ser FolderWeb etter matchende mapper og filer:
```
/content/
├── index.md → /
├── om/
│ └── index.md → /om/
└── blogg/
├── 2024-11-01-innlegg/
│ └── index.md → /blogg/innlegg/
└── index.md → /blogg/
```
## Automatiske funksjoner
**Mappenavn blir URL-er** Lag en mappe kalt `prosjekter` og den er umiddelbart tilgjengelig på `/prosjekter/`
**Datoprefikser fjernes** `2024-11-01-mitt-innlegg` blir `/mitt-innlegg/` i URL-en
**Egendefinerte slugs via metadata** Overstyr standard URL med `slug = "egendefinert-url"` i `metadata.ini`
**Avsluttende skråstrek** Kataloger omdirigerer alltid til å inkludere avsluttende skråstrek for konsistens
## Eksempel
Denne siden demonstrerer filbasert routing! Stien er:
```
app/default/content/examples/file-based-routing/index.no.md
```
Som rendres på:
```
/examples/file-based-routing/
```
Ingen ruter å definere. Ingen konfigurasjon å oppdatere. Bare filer og mapper.
## Fordeler
- **Intuitivt** Hvis du kan navigere mapper, forstår du routingen
- **Refaktorvennlig** Å flytte innhold betyr å flytte mapper
- **Ingen ødelagte lenker** URL-er matcher filsystemet
- **Rask** Ingen rutematching overhead, direkte filoppslag
- **Forutsigbar** Det du ser er det du får

View file

@ -0,0 +1,3 @@
title = "File-Based Routing"
summary = "Your folder structure is your URL structure—no configuration needed"
date = "2024-11-26"

View file

@ -0,0 +1,5 @@
# Examples
This section demonstrates different content types and features you can use in FolderWeb.
Each example below shows a different capability. Click through to see the source and how it works.

View file

@ -0,0 +1,11 @@
# Mix Formats
FolderWeb lets you **combine different file types** in the same directory. Markdown, HTML, and PHP files can coexist and render together on a single page.
## Supported Formats
- **`.md`** Markdown for content (this file!)
- **`.html`** Static HTML snippets
- **`.php`** Dynamic PHP components
All files in the same directory are rendered in **alphabetical order**. Use number prefixes (like `00-`, `01-`, `02-`) to control the sequence.

View file

@ -0,0 +1,11 @@
# Bland formater
FolderWeb lar deg **kombinere forskjellige filtyper** i samme katalog. Markdown, HTML og PHP-filer kan eksistere side om side og rendres sammen på en enkelt side.
## Støttede formater
- **`.md`** Markdown for innhold (denne filen!)
- **`.html`** Statiske HTML-snippets
- **`.php`** Dynamiske PHP-komponenter
Alle filer i samme katalog rendres i **alfabetisk rekkefølge**. Bruk talprefikser (som `00-`, `01-`, `02-`) for å kontrollere sekvensen.

View file

@ -0,0 +1,11 @@
<section style="background: var(--color-accent-light); padding: var(--space-m); border-radius: 0.5rem; margin-block: var(--space-m);">
<h2 style="margin-block-start: 0;">HTML Component</h2>
<p>This section is rendered from <code>01-html-example.html</code> a static HTML file with inline styles.</p>
<p>HTML files are perfect for:</p>
<ul>
<li>Custom-styled sections</li>
<li>Embedded media (video, audio, iframes)</li>
<li>Interactive elements (forms, details/summary)</li>
<li>SVG graphics and diagrams</li>
</ul>
</section>

View file

@ -0,0 +1,11 @@
<section style="background: var(--color-accent-light); padding: var(--space-m); border-radius: 0.5rem; margin-block: var(--space-m);">
<h2 style="margin-block-start: 0;">HTML-komponent</h2>
<p>Denne seksjonen rendres fra <code>01-html-example.no.html</code> en statisk HTML-fil med inline stiler.</p>
<p>HTML-filer er perfekte for:</p>
<ul>
<li>Egendefinerte stilte seksjoner</li>
<li>Innebygde medier (video, lyd, iframes)</li>
<li>Interaktive elementer (skjemaer, detaljer/sammendrag)</li>
<li>SVG-grafikk og diagrammer</li>
</ul>
</section>

View file

@ -0,0 +1,41 @@
<?php
$currentTime = date('H:i:s');
$serverInfo = [
'PHP-versjon' => PHP_VERSION,
'Servertid' => $currentTime,
'Filer i denne katalogen' => count(glob(__DIR__ . '/*')),
];
?>
<section style="background: linear-gradient(135deg, oklch(95% 0.08 150), oklch(98% 0.04 150)); padding: var(--space-m); border-radius: 0.5rem; margin-block: var(--space-m);">
<h2 style="margin-block-start: 0;">PHP-komponent</h2>
<p>Denne seksjonen rendres fra <code>02-dynamic.no.php</code> en dynamisk PHP-fil som kjører serversiden.</p>
<table style="width: 100%; margin-block: var(--space-s);">
<thead>
<tr>
<th>Egenskap</th>
<th>Verdi</th>
</tr>
</thead>
<tbody>
<?php foreach ($serverInfo as $key => $value): ?>
<tr>
<td><?= htmlspecialchars($key) ?></td>
<td><strong><?= htmlspecialchars($value) ?></strong></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p>PHP-filer kan:</p>
<ul>
<li>Generere dynamisk innhold</li>
<li>Spørre databaser</li>
<li>Lese fra filer eller API-er</li>
<li>Beregne verdier i farten</li>
<li>Inkludere egendefinert styling og logikk</li>
</ul>
<p><em>Last inn denne siden nytt for å se servertiden oppdatere seg!</em></p>
</section>

View file

@ -0,0 +1,41 @@
<?php
$currentTime = date('H:i:s');
$serverInfo = [
'PHP Version' => PHP_VERSION,
'Server Time' => $currentTime,
'Files in this directory' => count(glob(__DIR__ . '/*')),
];
?>
<section style="background: linear-gradient(135deg, oklch(95% 0.08 150), oklch(98% 0.04 150)); padding: var(--space-m); border-radius: 0.5rem; margin-block: var(--space-m);">
<h2 style="margin-block-start: 0;">PHP Component</h2>
<p>This section is rendered from <code>02-dynamic.php</code> a dynamic PHP file that executes server-side.</p>
<table style="width: 100%; margin-block: var(--space-s);">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<?php foreach ($serverInfo as $key => $value): ?>
<tr>
<td><?= htmlspecialchars($key) ?></td>
<td><strong><?= htmlspecialchars($value) ?></strong></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p>PHP files can:</p>
<ul>
<li>Generate dynamic content</li>
<li>Query databases</li>
<li>Read from files or APIs</li>
<li>Calculate values on-the-fly</li>
<li>Include custom styling and logic</li>
</ul>
<p><em>Refresh this page to see the server time update!</em></p>
</section>

View file

@ -0,0 +1,38 @@
## Why Mix Formats?
Different content types have different needs:
**Markdown** Fast authoring, readable source, perfect for articles and documentation
**HTML** Full control over structure, ideal for custom layouts and embedded media
**PHP** Dynamic content, server-side logic, database queries, real-time data
By mixing them freely, you get the best of all worlds. Write most content in Markdown for speed, add HTML for custom sections, and use PHP when you need dynamic behavior.
## This Page's Structure
This very page demonstrates format mixing:
```
/examples/mix-formats/
├── 00-intro.md (Markdown introduction)
├── 01-html-example.html (Static HTML component)
├── 02-dynamic.php (Dynamic PHP component)
├── 03-conclusion.md (Markdown conclusion)
└── metadata.ini (Page metadata)
```
All four content files render seamlessly as one cohesive page. The numbered prefixes ensure they appear in the intended order.
## Use Cases
**Blog with special sections** Markdown posts with custom HTML callouts or PHP-generated stats
**Documentation with demos** Markdown explanations with live PHP examples
**Landing pages** Mix markdown content with custom HTML heroes and PHP dynamic elements
**Portfolio sites** Markdown project descriptions with HTML galleries and PHP filtering
The flexibility is yours.

View file

@ -0,0 +1,38 @@
## Hvorfor blande formater?
Forskjellige innholdstyper har forskjellige behov:
**Markdown** Rask redigering, lesbar kilde, perfekt for artikler og dokumentasjon
**HTML** Full kontroll over struktur, ideelt for egendefinerte layout og innebygde medier
**PHP** Dynamisk innhold, server-side logikk, databasespørringer, sanntidsdata
Ved å blande dem fritt, får du det beste fra alle verdener. Skriv mesteparten av innholdet i Markdown for hastighet, legg til HTML for egendefinerte seksjoner, og bruk PHP når du trenger dynamisk oppførsel.
## Denne sidens struktur
Denne siden demonstrerer formatblanding:
```
/examples/mix-formats/
├── 00-intro.no.md (Markdown-introduksjon)
├── 01-html-example.no.html (Statisk HTML-komponent)
├── 02-dynamic.no.php (Dynamisk PHP-komponent)
├── 03-conclusion.no.md (Markdown-konklusjon)
└── metadata.ini (Sidemetadata)
```
Alle fire innholdsfilene rendres sømløst som én sammenhengende side. De nummererte prefiksene sikrer at de vises i ønsket rekkefølge.
## Bruksområder
**Blogg med spesialseksjoner** Markdown-innlegg med egendefinerte HTML-callouts eller PHP-generert statistikk
**Dokumentasjon med demoer** Markdown-forklaringer med live PHP-eksempler
**Landingssider** Bland markdown-innhold med egendefinerte HTML-heroer og PHP-dynamiske elementer
**Porteføljesider** Markdown-prosjektbeskrivelser med HTML-gallerier og PHP-filtrering
Fleksibiliteten er din.

View file

@ -0,0 +1,3 @@
title = "Mix Formats"
summary = "Combine Markdown, HTML, and PHP files to create rich, dynamic pages"
date = "2024-11-26"

View file

@ -0,0 +1,5 @@
# Nested Content
FolderWeb supports content nested as deep as you need. Your folder structure is your URL structure.
This section demonstrates multi-level nesting. Explore the subdirectories to see how it works.

View file

@ -0,0 +1,5 @@
# Level Two
This is two levels deep: `/examples/nested/level-two/`
You can organize content in hierarchies that make sense for your site structure.

View file

@ -0,0 +1,5 @@
# Level Three
This is three levels deep: `/examples/nested/level-two/level-three/`
The URL structure automatically follows your folder organization. No routing configuration needed.

View file

@ -0,0 +1,29 @@
# Level Four
Four levels deep: `/examples/nested/level-two/level-three/level-four/`
## How Deep Can You Go?
As deep as your filesystem allows. Each folder can contain:
- Content files (`.md`, `.html`, `.php`)
- Subdirectories (which become sub-URLs)
- Assets (images, PDFs, etc.)
- A `metadata.ini` file
- A `styles.css` file for page-specific styles
## Navigation
There's no automatic breadcrumb generation in the default theme, but you can add it in a custom template by parsing the URL path.
## Use Cases
**Documentation sites** Organize by category/subcategory/topic
**Photo galleries** Year/month/album/photo
**Course content** Course/module/lesson/exercise
**Product catalogs** Category/subcategory/product
The simplicity of file-based routing means your content organization is transparent and portable.

View file

@ -0,0 +1,47 @@
# No Build Step
Modern web development often involves complex build pipelines: transpilers, bundlers, minifiers, watchers, and more. FolderWeb takes a different approach: **edit a file, refresh the page, see the result**.
## What This Means
**No npm install** No package.json, no node_modules folder, no dependency hell
**No compilation** Write PHP, HTML, CSS, and Markdown directly
**No watching** No background processes monitoring file changes
**No bundling** Files are served as-is, leveraging HTTP/2 multiplexing
**No transpiling** Modern PHP 8.4+ and CSS work in all browsers
## The Development Flow
Traditional workflow:
```
Edit file → Save → Wait for build → Reload → See changes
```
FolderWeb workflow:
```
Edit file → Save → Reload → See changes
```
That's it. No waiting. No build errors. No cache invalidation headaches.
## But What About...
**Performance?** PHP is fast. No JavaScript means pages load instantly. CSS is cacheable with MD5 versioning.
**Minification?** HTTP/2 compression handles this. Serve from behind a CDN if needed.
**Modern CSS?** CSS nesting, custom properties, oklch() colors, grid—all native, all supported.
**Development speed?** Instant feedback loop beats any hot reload system.
## Why This Matters
Build steps add complexity. Every dependency is a potential breaking change. Every tool is another thing to learn, configure, and maintain.
FolderWeb will work the same way in 5 years, 10 years, even 20 years. Your content is just files. Your templates are just PHP. Your styles are just CSS.
**Simple lasts.**

View file

@ -0,0 +1,47 @@
# Uten byggesteg
Moderne webutvikling involverer ofte komplekse byggepipelines: transpilere, bundlere, minifisere, overvåkere og mer. FolderWeb tar en annen tilnærming: **rediger en fil, oppdater siden, se resultatet**.
## Hva dette betyr
**Ingen npm install** Ingen package.json, ingen node_modules-mappe, ingen avhengighetshelvete
**Ingen kompilering** Skriv PHP, HTML, CSS og Markdown direkte
**Ingen overvåkning** Ingen bakgrunnsprosesser som overvåker filendringer
**Ingen bundling** Filer serveres som de er, ved å utnytte HTTP/2-multipleksing
**Ingen transpilering** Moderne PHP 8.4+ og CSS fungerer i alle nettlesere
## Utviklingsflyten
Tradisjonell arbeidsflyt:
```
Rediger fil → Lagre → Vent på bygg → Last inn på nytt → Se endringer
```
FolderWeb-arbeidsflyt:
```
Rediger fil → Lagre → Last inn på nytt → Se endringer
```
Det er det. Ingen venting. Ingen byggefeil. Ingen cache-invalideringshodebryet.
## Men hva med...
**Ytelse?** PHP er rask. Ingen JavaScript betyr at sider lastes øyeblikkelig. CSS er cachebar med MD5-versjonering.
**Minifisering?** HTTP/2-kompresjon håndterer dette. Server fra bak en CDN om nødvendig.
**Moderne CSS?** CSS-nesting, custom properties, oklch()-farger, grid—alt native, alt støttet.
**Utviklingshastighet?** Øyeblikkelig tilbakemeldingssløyfe slår ethvert hot reload-system.
## Hvorfor dette betyr noe
Byggesteg legger til kompleksitet. Hver avhengighet er en potensiell breaking change. Hvert verktøy er noe nytt å lære, konfigurere og vedlikeholde.
FolderWeb vil fungere på samme måte om 5 år, 10 år, til og med 20 år. Innholdet ditt er bare filer. Malene dine er bare PHP. Stilene dine er bare CSS.
**Enkelt varer.**

View file

@ -0,0 +1,3 @@
title = "No Build Step"
summary = "Edit, save, refresh—that's it. No compilation, no bundling, no waiting"
date = "2024-11-26"

View file

@ -0,0 +1,3 @@
# Oldest Post
The oldest post in this compact list.

View file

@ -0,0 +1,3 @@
# Older Post
An older post showing date ordering.

View file

@ -0,0 +1,3 @@
# Recent Post
A recent example post.

View file

@ -0,0 +1,3 @@
# Compact Template
This section uses `list-compact.php` for a minimal, clean list view.

View file

@ -0,0 +1,4 @@
title = "Compact Template Example"
[settings]
page_template = "list-compact"

View file

@ -0,0 +1,3 @@
# Grid Template
This section uses `list-grid.php` to display items in a responsive grid with cards.

View file

@ -0,0 +1,3 @@
# Grid Item One
This is an example item in the grid layout.

View file

@ -0,0 +1 @@
summary = "First item demonstrating grid layout"

View file

@ -0,0 +1,3 @@
# Grid Item Three
A third item to show the grid layout with multiple items.

View file

@ -0,0 +1 @@
summary = "Third grid item"

View file

@ -0,0 +1,3 @@
# Grid Item Two
Another example showing how items appear in the grid.

View file

@ -0,0 +1 @@
summary = "Second item in the grid"

View file

@ -0,0 +1,4 @@
title = "Grid Template Example"
[settings]
page_template = "list-grid"

View file

@ -0,0 +1,24 @@
# List Templates
FolderWeb includes several list templates you can use for different presentation styles.
This section demonstrates the different templates by using subdirectories. Each subdirectory below uses a different template via `metadata.ini`.
## Available Templates
**list.php** Default linear list with optional cover images
**list-grid.php** Grid layout with cards, great for galleries or portfolios
**list-compact.php** Minimal list showing just titles and dates
## Choosing a Template
In any folder's `metadata.ini`, add:
```ini
[settings]
page_template = "list-grid"
```
The three subdirectories below each use a different template. Compare them to see which fits your content best.

View file

@ -0,0 +1,53 @@
# Multilingual Support
**Currently viewing: English (EN)**
This page demonstrates FolderWeb's built-in language support. Use the language switcher in the header to toggle between **EN** and **NO** - the page content will change to match your selected language.
## How It Works
Creating multilingual content is straightforward:
1. **Add language codes to filenames** Create `index.en.md` and `index.no.md`
2. **Language switcher appears automatically** When multiple versions exist
3. **URLs get language prefixes** Non-default languages use `/en/page/`, `/no/page/`, etc.
4. **Share metadata** One `metadata.ini` file per directory works for all languages
## File Structure Example
```
/content/about/
├── index.en.md # English version
├── index.no.md # Norwegian version
└── metadata.ini # Shared metadata
```
## Configuration
Enable languages in `/app/default/config.ini`:
```ini
[languages]
default = "en"
available = "en,no"
```
## Translation Strings
UI text comes from language files in `/app/default/languages/`:
```ini
; en.ini
home = "Home"
read_more = "Read more"
; no.ini
home = "Hjem"
read_more = "Les mer"
```
Translation strings are optional—templates fall back to sensible defaults if missing.
## Try It
Use the language switcher in the header to toggle between English and Norwegian versions of this page. The content stays the same, just translated.

View file

@ -0,0 +1,53 @@
# Flerspråklig støtte
**Viser nå: Norsk (NO)**
Denne siden demonstrerer FolderWebs innebygde språkstøtte. Bruk språkvelgeren i headeren for å bytte mellom **EN** og **NO** - sideinnholdet vil endre seg til å matche det valgte språket.
## Slik fungerer det
Å opprette flerspråklig innhold er enkelt:
1. **Legg til språkkoder i filnavn** Opprett `index.en.md` og `index.no.md`
2. **Språkvelgeren vises automatisk** Når flere versjoner finnes
3. **URL-er får språkprefikser** Ikke-standardspråk bruker `/en/side/`, `/no/side/`, osv.
4. **Del metadata** Én `metadata.ini`-fil per katalog fungerer for alle språk
## Eksempel på filstruktur
```
/content/om/
├── index.en.md # Engelsk versjon
├── index.no.md # Norsk versjon
└── metadata.ini # Delt metadata
```
## Konfigurasjon
Aktiver språk i `/app/default/config.ini`:
```ini
[languages]
default = "en"
available = "en,no"
```
## Oversettelsesstrenger
UI-tekst kommer fra språkfiler i `/app/default/languages/`:
```ini
; en.ini
home = "Home"
read_more = "Read more"
; no.ini
home = "Hjem"
read_more = "Les mer"
```
Oversettelsesstrenger er valgfrie—maler faller tilbake til fornuftige standardverdier hvis de mangler.
## Prøv det
Bruk språkvelgeren i headeren for å bytte mellom engelske og norske versjoner av denne siden. Innholdet forblir det samme, bare oversatt.

View file

@ -0,0 +1,2 @@
title = "Multilingual Support"
summary = "Learn how to create content in multiple languages"

View file

@ -1,34 +1,112 @@
/* Page-specific styles for homepage */
/* Hero section for frontpage */
.hero {
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 1rem .4rem;
background-color: oklch(0.85 0.05 250);
min-height: 40vh;
text-align: center;
background: linear-gradient(135deg, oklch(95% 0.05 250) 0%, oklch(98% 0.02 250) 100%);
border-radius: 0.75rem;
padding: var(--space-m);
margin-block: var(--space-s);
text-align: center;
& .hero-title {
font-size: clamp(2rem, 5vw, 2.5rem);
margin-block: 0 var(--space-xs);
background: linear-gradient(135deg, oklch(40% 0.15 250), oklch(30% 0.2 280));
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 900;
}
& .hero-subtitle {
font-size: clamp(1rem, 2vw, 1.125rem);
color: var(--color-muted);
margin-block-end: var(--space-m);
max-width: 32rem;
margin-inline: auto;
}
& .cta-button {
display: inline-block;
background: var(--color-accent);
color: white;
padding: var(--space-xs) var(--space-m);
border-radius: 0.5rem;
text-decoration: none;
font-weight: 600;
margin-block-start: var(--space-xs);
&:hover {
background: oklch(45% 0.15 250);
color: white;
}
}
}
.hero h1 {
font-size: clamp(2.5rem, 6vw, 4rem);
margin: 0;
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
gap: var(--space-s);
margin-block: var(--space-m);
& .feature-card {
background: var(--color-background);
padding: var(--space-s);
border-radius: 0.5rem;
border: 1px solid var(--color-border);
text-align: center;
transition: transform 0.2s, box-shadow 0.2s;
text-decoration: none;
color: inherit;
display: block;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px oklch(0% 0 0 / 0.1);
border-color: var(--color-accent);
}
& .feature-icon {
font-size: 2rem;
display: block;
margin-block-end: var(--space-xs);
}
& .feature-title {
font-weight: bold;
margin-block: var(--space-xs);
color: var(--color-text);
font-size: 0.9rem;
}
& .feature-description {
font-size: 0.85rem;
color: var(--color-muted);
margin: 0;
}
}
}
.cta-section {
background: linear-gradient(135deg, oklch(0.65 0.15 250), oklch(0.50 0.12 250));
padding: 2rem 1rem;
margin-top: 3rem;
text-align: center;
color: white;
}
.cta-section p {
color: white;
font-size: clamp(1.1rem, 3vw, 1.3rem);
}
.cta-content {
max-width: 42rem;
margin: 0 auto;
.stats {
display: flex;
justify-content: center;
gap: var(--space-m);
flex-wrap: wrap;
margin-block-start: var(--space-m);
padding-block-start: var(--space-s);
border-block-start: 1px solid var(--color-border);
& .stat {
text-align: center;
& .stat-value {
display: block;
font-size: 1.25rem;
font-weight: bold;
color: var(--color-accent);
}
& .stat-label {
font-size: 0.8rem;
color: var(--color-muted);
}
}
}

View file

@ -1,13 +1,13 @@
; English translations
; These strings are used in templates and can be customized
; If a string is missing, the key name will be used as fallback
home = "Home"
categories = "Categories"
tags = "Tags"
read_more = "Read more"
read_article = "Read article"
read_full_answer = "Read full answer"
download_pdf = "Download PDF"
summary = "Summary"
footer_text = "Footer content goes here"
footer_handcoded = "This page was generated in"
footer_handcoded = "Generated in"
footer_page_time = "ms"
months = "January,February,March,April,May,June,July,August,September,October,November,December"

View file

@ -1,13 +1,13 @@
; Norwegian translations
; These strings are used in templates and can be customized
; If a string is missing, English fallback will be used
home = "Hjem"
categories = "Kategorier"
tags = "Stikkord"
tags = "Emneknagger"
read_more = "Les mer"
read_article = "Les artikkel"
read_full_answer = "Les hele svaret"
download_pdf = "Last ned PDF"
summary = "Oppsummering"
footer_text = "Bunntekst her"
footer_handcoded = "Denne siden ble generert på"
summary = "Sammendrag"
footer_handcoded = "Generert på"
footer_page_time = "ms"
months = "januar,februar,mars,april,mai,juni,juli,august,september,oktober,november,desember"

View file

@ -1,191 +0,0 @@
/* MINIMAL CSS RESET*/
* { margin-bottom: 0; }
/* VARIABLES */
:root {
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-heading: Georgia, "Times New Roman", serif;
--color-primary: #4a90e2;
--color-primary: oklch(0.65 0.15 250);
--color-secondary: #2c5aa0;
--color-secondary: oklch(0.50 0.12 250);
--color-light: #f0f4f8;
--color-light: oklch(0.97 0.01 250);
--color-grey: #404040;
--color-grey: oklch(0.37 0 0);
}
/* GLOBAL */
html { font-family: var(--font-body); font-size: clamp(16px, 2.3vw, 20px); scroll-behavior: smooth; }
body { margin: 0; color: var(--color-grey) }
p, ul, ol, aside { line-height: 1.5em; hyphens: auto }
img { max-width: 100%; height: auto; }
h1 { color: var(--color-primary); font-size: 2.3rem }
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
font-weight: 400;
line-height: 1.3em;
margin-top: 1.3em;
text-wrap: pretty;
}
a {
color: var(--color-primary);
text-decoration: none;
&:hover { color: var(--color-secondary) }
}
.grid-container {
display: grid;
grid-template-rows: auto 1fr auto;
grid-template-columns: 1fr;
grid-template-areas: "header" "main" "footer";
height: 100%;
width: 100%;
justify-content: center;
min-height: 100vh;
align-items: stretch;
}
.contain, :where(main>article, main>aside, main>section) {
display: grid;
grid-template-columns: minmax(.4rem, 1fr) minmax(0, 42rem) minmax(.3rem, 1fr);
> * {
grid-column: 2;
}
}
.escape {
grid-column: 1 / -1 !important;
}
/* HEADER */
header {
border-bottom: 3px #00000022 solid;
grid-area: header;
> div {
padding-bottom: .2rem;
display: flex;
.logo {
margin-right: .3rem;
svg {
width: 7rem;
height: 100%;
color: var(--color-primary);
}
}
nav {
display:flex;
align-items: center;
justify-content:flex-end;
flex: 1;
ul {
display: flex;
list-style: none;
flex-wrap: wrap;
margin-top: .4rem;
padding: 0;
justify-content: flex-end;
li {
margin-left:0.4rem;
margin-top:0.4rem;
}
}
}
}
}
/* MAIN */
main {
grid-area: main;
background-color: var(--color-light);
padding-bottom: 2rem;
aside { margin-top: 1.3em }
article {
.intro {
font-size: 1.2rem;
line-height: 1.35em;
}
}
.button {
margin-top: 1.3rem;
justify-self: start;
}
}
/* BUTTONS */
.button {
display: inline-block;
text-decoration: none;
border-radius: 2rem;
padding: 0.35rem 1rem;
background-color: transparent;
color: var(--color-grey);
outline: 0.08rem var(--color-grey) solid;
&:hover {
background-color: var(--color-grey);
color: white;
outline: none;
}
&:active, &.active {
background-color: var(--color-primary);
color: white;
outline: none;
}
&:focus {
background-color: var(--color-primary);
color: white;
outline: none;
}
&.inverted {
background-color: transparent;
color: white;
outline: 0.08rem white solid;
&:hover {
background-color: white;
color: var(--color-primary);
outline: none;
}
&:active, &.active {
background-color: var(--color-light);
color: var(--color-primary);
outline: none;
}
&:focus {
color: white;
background-color: var(--color-grey);
outline: none;
}
}
&.bigger {
font-size: 1.2em;
padding: calc(0.35rem * 1.2) calc(1rem * 1.2);
border-radius: calc(1rem * 1.2);
}
}
/* FOOTER */
footer {
color: var(--color-light);
a {
color: var(--color-light);
&:hover { color: white; text-decoration: underline }
}
background-color: var(--color-secondary);
grid-area: footer;
> div {
margin: 1rem 0;
text-align: center;
.generated { font-size: .6rem }
}
}

View file

@ -1,69 +1,55 @@
<?php
$startTime = microtime(true);
$publicDir = realpath($_SERVER['DOCUMENT_ROOT']);
$customCssPath = __DIR__ . '/../../custom/styles/base.css';
$defaultCssPath = __DIR__ . '/../styles/base.css';
$cssPath = file_exists($customCssPath) ? $customCssPath : $defaultCssPath;
$cssUrl = file_exists($customCssPath) ? '/app/styles/base.css' : '/app/default-styles/base.css';
$cssHash = file_exists($cssPath) ? hash_file('md5', $cssPath) : 'file_not_found';
if (isset($GLOBALS['_SERVER']['SCRIPT_FILENAME'])) { $includingFile = $_SERVER['SCRIPT_FILENAME']; }
if (!empty($includingFile)) { $pageName = pathinfo($includingFile, PATHINFO_FILENAME); }
if (!in_array(basename(dirname($includingFile)), ['latest', 'live', 'frozen']) && basename(dirname($includingFile)) !== '') { $dirName = basename(dirname($includingFile)); }
function getActiveClass($href) { return rtrim(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), '/') === rtrim($href, '/') ? 'active' : ''; }
?>
<!DOCTYPE html>
<html lang="<?= htmlspecialchars($currentLang ?? 'en') ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?php if (!empty($metaDescription)): ?>
<meta name="description" content="<?= htmlspecialchars($metaDescription) ?>">
<?php endif; ?>
<link rel="stylesheet" href="<?= $cssUrl ?>?v=<?= $cssHash ?>">
<?php if (!empty($pageCssUrl)): ?>
<link rel="stylesheet" href="<?= $pageCssUrl ?>?v=<?= $pageCssHash ?? '' ?>">
<?php endif; ?>
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="icon" href="/favicon.png" type="image/png">
<title><?= htmlspecialchars($pageTitle ?? 'Site Title') ?></title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars($pageTitle ?? 'FolderWeb') ?></title>
<?php if (!empty($metaDescription)): ?>
<meta name="description" content="<?= htmlspecialchars($metaDescription) ?>">
<?php endif; ?>
<?php if (!empty($socialImageUrl)): ?>
<meta property="og:image" content="<?= htmlspecialchars($socialImageUrl) ?>">
<?php endif; ?>
<link rel="stylesheet" href="/app/default/styles/styles.css">
<?php if (!empty($pageCssUrl)): ?>
<link rel="stylesheet" href="<?= htmlspecialchars($pageCssUrl) ?>?v=<?= htmlspecialchars($pageCssHash ?? '') ?>">
<?php endif; ?>
</head>
<body>
<header>
<nav>
<a href="<?= htmlspecialchars($langPrefix ?? '') ?>/"><?= htmlspecialchars($homeLabel ?? ($translations['home'] ?? 'Home')) ?></a>
<?php if (!empty($navigation)): ?>
<?php foreach ($navigation as $item): ?>
<a href="<?= htmlspecialchars($item['url']) ?>"><?= htmlspecialchars($item['title']) ?></a>
<?php endforeach; ?>
<?php endif; ?>
</nav>
<?php if (!empty($languageUrls) && count($languageUrls) > 1): ?>
<nav class="language-switcher" aria-label="Language">
<?php foreach ($languageUrls as $lang => $url): ?>
<a href="<?= htmlspecialchars($url) ?>" <?= ($lang === $currentLang) ? 'aria-current="true"' : '' ?>><?= htmlspecialchars(strtoupper($lang)) ?></a>
<?php endforeach; ?>
</nav>
<?php endif; ?>
</header>
<body class="<?php if (isset($dirName)) echo 'section-' . $dirName . ' '; ?><?php if (isset($pageName)) echo 'page-' . $pageName; ?>">
<div class="grid-container">
<header class="contain">
<div>
<div class="logo">
<a href="/">
<svg width="200" height="60" viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg">
<text x="10" y="40" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="currentColor">FolderWeb</text>
</svg>
</a>
</div>
<nav>
<ul>
<li><a href="/" class="button <?php echo getActiveClass('/'); ?>"><?= htmlspecialchars($translations['home'] ?? 'Home') ?></a></li>
<?php if (!empty($navigation)): ?>
<?php foreach ($navigation as $item): ?>
<li><a href="<?= htmlspecialchars($item['url']) ?>" class="button <?php echo getActiveClass($item['url']); ?>"><?= htmlspecialchars($item['title']) ?></a></li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</nav>
</div>
</header>
<main>
<?= $content ?>
</main>
<main>
<?php echo $content ?? ''; ?>
</main>
<footer>
<div class="contain">
<p><?= htmlspecialchars($translations['footer_text'] ?? 'Footer content goes here') ?></p>
<?php $endTime = microtime(true); $pageLoadTime = round(($endTime - $startTime) * 1000, 2); ?>
<p class="generated"><?= htmlspecialchars($translations['footer_handcoded'] ?? 'This page was generated in') ?> <?php echo $pageLoadTime; ?><?= htmlspecialchars($translations['footer_page_time'] ?? 'ms') ?></p>
</div>
</footer>
</div>
<footer>
<nav>
<a href="https://mastodon.social/@example" rel="me">Mastodon</a>
<a href="https://bsky.app/profile/example.bsky.social">Bluesky</a>
</nav>
<p>
<?php if (!empty($translations['footer_handcoded'])): ?>
<?= htmlspecialchars($translations['footer_handcoded']) ?> <?= number_format((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2) ?><?= htmlspecialchars($translations['footer_page_time'] ?? 'ms') ?>
<?php else: ?>
Generated in <?= number_format((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']) * 1000, 2) ?>ms
<?php endif; ?>
</p>
</footer>
</body>
</html>

View file

@ -1,82 +0,0 @@
<?php if (!empty($pageContent)): ?>
<article class="list-intro">
<?= $pageContent ?>
</article>
<?php endif; ?>
<section class="list-card-grid-wrapper">
<div class="list-card-grid">
<?php foreach ($items as $item): ?>
<article>
<?php if ($item['cover']): ?>
<a href="<?= htmlspecialchars($item['pdf'] ?? $item['url']) ?>">
<img src="<?= htmlspecialchars($item['cover']) ?>" alt="<?= htmlspecialchars($item['title']) ?>">
</a>
<?php endif; ?>
<h1>
<a href="<?= htmlspecialchars($item['redirect'] ?? $item['url']) ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</h1>
<?php if (($metadata['show_date'] ?? true) && !empty($item['date'])): ?>
<p><?= htmlspecialchars($item['date']) ?></p>
<?php endif; ?>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<div class="card-actions">
<?php if (!empty($item['pdf'])): ?>
<a href="<?= htmlspecialchars($item['pdf']) ?>" class="button" download><?= htmlspecialchars($translations['download_pdf'] ?? 'Download PDF') ?></a>
<?php endif; ?>
<?php if (!empty($item['redirect'])): ?>
<a href="<?= htmlspecialchars($item['redirect']) ?>" class="button"><?= htmlspecialchars($translations['read_article'] ?? 'Read article') ?></a>
<?php else: ?>
<a href="<?= htmlspecialchars($item['url']) ?>" class="button"><?= htmlspecialchars($translations['read_more'] ?? 'Read more') ?></a>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<style>
main > section.list-card-grid-wrapper {
margin-top: 1.3em;
.list-card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(15rem, 45%, 20rem), 1fr));
gap: clamp(1rem, 3vw, 2rem);
}
.list-card-grid > article {
background-color: white;
padding: 0;
overflow: hidden;
display: flex;
flex-direction: column;
> :not(img, a) {
padding-left: 1rem;
padding-right: 1rem;
}
h1 {
margin-top: 1rem;
font-size: clamp(1.5rem, 4vw, 2rem);
}
.card-actions {
margin-top: auto;
display: flex;
gap: 0.3rem;
flex-wrap: wrap;
padding-bottom: 1rem;
a {
margin-top: 1.3rem;
}
}
}
}
</style>

View file

@ -0,0 +1,61 @@
<?php if (!empty($pageContent)): ?>
<div class="list-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<ul class="compact-list">
<?php foreach ($items as $item): ?>
<li>
<a href="<?= htmlspecialchars($item['url']) ?>">
<strong><?= htmlspecialchars($item['title']) ?></strong>
<?php if (($metadata['show_date'] ?? true) && !empty($item['date'])): ?>
<time><?= htmlspecialchars($item['date']) ?></time>
<?php endif; ?>
</a>
<?php if (!empty($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<style>
.compact-list {
list-style: none;
padding: 0;
max-width: var(--size-content);
& li {
border-block-end: 1px solid var(--color-border);
padding-block: var(--space-s);
&:first-child {
padding-block-start: 0;
}
}
& a {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--space-s);
text-decoration: none;
&:hover strong {
text-decoration: underline;
}
}
& time {
white-space: nowrap;
font-size: 0.875rem;
}
& p {
margin-block-start: var(--space-xs);
color: var(--color-muted);
font-size: 0.9rem;
}
}
</style>

View file

@ -1,141 +0,0 @@
<?php if (!empty($pageContent)): ?>
<article class="list-intro">
<?= $pageContent ?>
</article>
<?php endif; ?>
<section class="list-faq-wrapper">
<div class="list-faq">
<?php foreach ($items as $item): ?>
<details class="faq-item">
<summary>
<h2><?= htmlspecialchars($item['title']) ?></h2>
<span class="toggle-icon" aria-hidden="true"></span>
</summary>
<div class="faq-content">
<?php if ($item['summary']): ?>
<p><strong><?= htmlspecialchars($translations['summary'] ?? 'Summary') ?>:</strong> <?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<a href="<?= htmlspecialchars($item['url']) ?>" class="button"><?= htmlspecialchars($translations['read_full_answer'] ?? 'Read full answer') ?></a>
</div>
</details>
<?php endforeach; ?>
</div>
</section>
<style>
main > section.list-faq-wrapper {
margin-top: 1.3em;
.list-faq {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.faq-item {
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 0.5rem;
overflow: hidden;
transition: border-color 0.2s ease;
&:hover {
border-color: var(--color-primary);
}
&[open] {
border-color: var(--color-primary);
}
}
summary {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
cursor: pointer;
list-style: none;
user-select: none;
&::-webkit-details-marker {
display: none;
}
h2 {
margin: 0;
font-size: 1.3rem;
font-weight: 400;
color: var(--color-grey);
flex: 1;
}
.toggle-icon {
width: 1.5rem;
height: 1.5rem;
flex-shrink: 0;
position: relative;
margin-left: 1rem;
&::before,
&::after {
content: '';
position: absolute;
background-color: var(--color-primary);
transition: transform 0.3s ease;
}
&::before {
top: 50%;
left: 0;
right: 0;
height: 2px;
transform: translateY(-50%);
}
&::after {
left: 50%;
top: 0;
bottom: 0;
width: 2px;
transform: translateX(-50%);
}
}
&:hover h2 {
color: var(--color-primary);
}
}
.faq-item[open] summary .toggle-icon::after {
transform: translateX(-50%) rotate(90deg);
opacity: 0;
}
.faq-content {
padding: 0 1.5rem 1.5rem 1.5rem;
animation: slideDown 0.3s ease;
p {
margin-top: 0;
color: var(--color-grey);
line-height: 1.6;
}
.button {
margin-top: 1rem;
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-0.5rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
}
</style>

View file

@ -1,94 +1,48 @@
<?php if (!empty($pageContent)): ?>
<article class="list-intro">
<?= $pageContent ?>
</article>
<div class="list-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<section class="list-grid-wrapper">
<div class="list-grid">
<?php foreach ($items as $item): ?>
<article>
<?php if ($item['cover']): ?>
<a href="<?= htmlspecialchars($item['url']) ?>">
<img src="<?= htmlspecialchars($item['cover']) ?>" alt="<?= htmlspecialchars($item['title']) ?>">
</a>
<?php endif; ?>
<h1>
<a href="<?= htmlspecialchars($item['url']) ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</h1>
<?php if (($metadata['show_date'] ?? true) && !empty($item['date'])): ?>
<p><?= htmlspecialchars($item['date']) ?></p>
<?php endif; ?>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<div class="grid-actions">
<?php if (!empty($item['pdf'])): ?>
<a href="<?= htmlspecialchars($item['pdf']) ?>" class="button" download><?= htmlspecialchars($translations['download_pdf'] ?? 'Download PDF') ?></a>
<?php endif; ?>
<a href="<?= htmlspecialchars($item['url']) ?>" class="button"><?= htmlspecialchars($translations['read_more'] ?? 'Read more') ?></a>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<div class="grid">
<?php foreach ($items as $item): ?>
<article class="card">
<?php if (!empty($item['cover'])): ?>
<img src="<?= htmlspecialchars($item['cover']) ?>" alt="">
<?php endif; ?>
<h3><a href="<?= htmlspecialchars($item['url']) ?>"><?= htmlspecialchars($item['title']) ?></a></h3>
<?php if (($metadata['show_date'] ?? true) && !empty($item['date'])): ?>
<time><?= htmlspecialchars($item['date']) ?></time>
<?php endif; ?>
<?php if (!empty($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
<style>
main > section.list-grid-wrapper {
margin-top: 1.3em;
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
gap: var(--space-m);
}
.list-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(clamp(15rem, 45%, 20rem), 1fr));
gap: clamp(1rem, 3vw, 2rem);
}
.list-grid > article {
background-color: white;
padding: 0;
overflow: hidden;
display: flex;
flex-direction: column;
> :not(img, a) {
padding-left: 1rem;
padding-right: 1rem;
}
h1 {
margin-top: 1rem;
font-size: clamp(1.5rem, 4vw, 2rem);
}
.grid-actions {
margin-top: auto;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
padding-bottom: 1rem;
a {
margin-top: 1.3rem;
border-radius: 1rem;
background-color: var(--color-grey);
padding: 0.35rem 1rem;
color: white;
text-decoration: none;
&:hover {
background-color: var(--color-primary);
color: white;
}
&:focus {
outline: 0.1rem var(--color-primary) solid;
color: var(--color-grey);
background-color: white;
}
}
}
}
.card {
border: 1px solid var(--color-border);
border-radius: 0.5rem;
padding: var(--space-s);
& img {
margin-block-end: var(--space-s);
border-radius: 0.25rem;
}
& h3 {
margin-block-start: 0;
}
}
</style>

View file

@ -1,55 +1,25 @@
<?php if (!empty($pageContent)): ?>
<article class="list-intro">
<?= $pageContent ?>
</article>
<div class="list-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<article>
<?php foreach ($items as $item): ?>
<article>
<?php if ($item['cover']): ?>
<a href="<?= htmlspecialchars($item['url']) ?>">
<img src="<?= htmlspecialchars($item['cover']) ?>" alt="<?= htmlspecialchars($item['title']) ?>">
</a>
<?php endif; ?>
<h1>
<a href="<?= htmlspecialchars($item['url']) ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</h1>
<?php if (($metadata['show_date'] ?? true) && !empty($item['date'])): ?>
<p><?= htmlspecialchars($item['date']) ?></p>
<?php endif; ?>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<a href="<?= htmlspecialchars($item['url']) ?>" class="button"><?= htmlspecialchars($translations['read_more'] ?? 'Read more') ?></a>
</article>
<?php endforeach; ?>
</article>
<style>
main > article {
> article {
background-color: white;
padding: 0;
padding-bottom: 1.3rem;
margin-bottom: 1.5rem;
overflow: hidden;
> :not(img, a) {
padding-left: 1rem;
padding-right: 1rem;
}
h1 {
margin-top: 1rem;
}
> .button {
margin-left: 1rem;
margin-top: 1rem;
}
}
}
</style>
<div class="list">
<?php foreach ($items as $item): ?>
<article class="list-item">
<?php if (!empty($item['cover'])): ?>
<img src="<?= htmlspecialchars($item['cover']) ?>" alt="">
<?php endif; ?>
<h2><a href="<?= htmlspecialchars($item['url']) ?>"><?= htmlspecialchars($item['title']) ?></a></h2>
<?php if (($metadata['show_date'] ?? true) && !empty($item['date'])): ?>
<time><?= htmlspecialchars($item['date']) ?></time>
<?php endif; ?>
<?php if (!empty($item['summary'])): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>

View file

@ -1,25 +1,3 @@
<?= $content ?>
<?php if ($pageMetadata && (isset($pageMetadata['tags']) || isset($pageMetadata['categories']))): ?>
<aside class="metadata">
<?php if (!empty($pageMetadata['categories'])): ?>
<div class="categories">
<strong><?= htmlspecialchars($translations['categories'] ?? 'Categories') ?>:</strong>
<?php
$categories = array_map('trim', explode(',', $pageMetadata['categories']));
echo implode(', ', array_map('htmlspecialchars', $categories));
?>
</div>
<?php endif; ?>
<?php if (!empty($pageMetadata['tags'])): ?>
<div class="tags">
<strong><?= htmlspecialchars($translations['tags'] ?? 'Tags') ?>:</strong>
<?php
$tags = array_map('trim', explode(',', $pageMetadata['tags']));
echo implode(', ', array_map('htmlspecialchars', $tags));
?>
</div>
<?php endif; ?>
</aside>
<?php endif; ?>
<article>
<?= $content ?>
</article>