This commit is contained in:
2026-05-13 21:17:36 +03:00
parent e43b94ba20
commit 3a6ab777c9
49 changed files with 9247 additions and 210 deletions

68
app/views/about/index.md Normal file
View File

@@ -0,0 +1,68 @@
# About Framex
Hi, I am **Thanos**. I have been involved with computers, hardware, and practical IT work since 1995. Framex is a small PHP project I built and refined through real use, mostly for fast static pages, custom CSS experiments, presentations, API-driven pages, and lightweight content sites.
![Thanos](../images/thanos.png)
## Why this project exists
Framex is built around one simple idea: a website should be easy to understand from its files.
Routes map to files in `app/views`, templates live in `templates`, public assets live in `public`, and styling is handled through Tailwind CSS 4 or any CSS approach you prefer. There is no heavy framework layer to learn before you can build a page.
## What it is good for
- **Static websites** that need clean URLs and reusable templates.
- **Markdown documentation** with automatic typography and dark-mode support.
- **Small business websites** where speed and maintainability matter.
- **Custom landing pages** with modern Tailwind components.
- **API-powered pages** when you need PHP logic without a large framework.
- **Presentations and prototypes** that should stay portable and simple.
## Design goals
Framex tries to stay:
- **Lightweight:** only the pieces needed to render pages clearly.
- **Fast:** no unnecessary runtime complexity.
- **Readable:** routes, views, templates, and assets are easy to find.
- **Flexible:** use PHP views, Markdown views, Tailwind CSS, or plain CSS.
- **Practical:** built for developers who want to ship pages without ceremony.
## How pages work
A URL such as `/about` resolves to a file like:
```text
app/views/about/index.md
```
A URL such as `/demo/blog-post` resolves to:
```text
app/views/demo/blog-post.php
```
Markdown pages are automatically parsed and styled. PHP pages can use custom markup, arrays, loops, metadata, and reusable helpers.
## Usage
You can use, extend, modify, reduce, or rebuild this project for personal and commercial work. Treat it as a starting point, not a locked system.
Good next steps:
- Read the [documentation](/docs).
- Explore the [demo pages](/demo).
- Create a new PHP view in `app/views`.
- Create a new Markdown page with `index.md`.
- Rebuild CSS with `npm run build:css` when you add new Tailwind classes.
## Disclaimer
This is a personal project shared for developers and people who understand how to work with PHP projects. I have used it for years without issues, but you are responsible for how you use it, modify it, deploy it, and secure it.
Use it carefully, make backups, and review the code before using it in production.
**Enjoy building.**

View File

@@ -0,0 +1,53 @@
<div class="section">
<div class="band">
<div class="card space-y-4">
<details class="group [&amp;_summary::-webkit-details-marker]:hidden">
<summary class="flex items-center justify-between gap-1.5 rounded-md border border-gray-100 dark:border-slate-800 bg-gray-50 dark:bg-slate-900 p-4 text-gray-900 dark:text-slate-300 ">
<h2 class="text-lg font-medium">Lorem ipsum dolor sit amet consectetur adipisicing?</h2>
<svg class="size-5 shrink-0 transition-transform duration-300 group-open:-rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</summary>
<p class="px-4 pt-4 text-gray-900 dark:text-slate-300">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ab hic veritatis molestias culpa
in, recusandae laboriosam neque aliquid libero nesciunt voluptate dicta quo officiis
explicabo consequuntur distinctio corporis earum similique!
</p>
</details>
<details class="group [&amp;_summary::-webkit-details-marker]:hidden">
<summary class="flex items-center justify-between gap-1.5 rounded-md border border-gray-100 dark:border-slate-800 bg-gray-50 dark:bg-slate-900 p-4 text-gray-900 dark:text-slate-300 ">
<h2 class="text-lg font-medium">Lorem ipsum dolor sit amet consectetur adipisicing?</h2>
<svg class="size-5 shrink-0 transition-transform duration-300 group-open:-rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</summary>
<p class="px-4 pt-4 text-gray-900 dark:text-slate-300">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ab hic veritatis molestias culpa
in, recusandae laboriosam neque aliquid libero nesciunt voluptate dicta quo officiis
explicabo consequuntur distinctio corporis earum similique!
</p>
</details>
<details class="group [&amp;_summary::-webkit-details-marker]:hidden">
<summary class="flex items-center justify-between gap-1.5 rounded-md border border-gray-100 dark:border-slate-800 bg-gray-50 dark:bg-slate-900 p-4 text-gray-900 dark:text-slate-300 ">
<h2 class="text-lg font-medium">Lorem ipsum dolor sit amet consectetur adipisicing?</h2>
<svg class="size-5 shrink-0 transition-transform duration-300 group-open:-rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</summary>
<p class="px-4 pt-4 text-gray-900 dark:text-slate-300">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ab hic veritatis molestias culpa
in, recusandae laboriosam neque aliquid libero nesciunt voluptate dicta quo officiis
explicabo consequuntur distinctio corporis earum similique!
</p>
</details>
</div>
</div>
</div>

View File

@@ -0,0 +1,77 @@
<section class="section">
<div class="band">
<div class="card">
<div class="entry-section">
<?php
$serverURL = rtrim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), '/') ?: '/';
$parentURL = $serverURL === '/' ? '/' : dirname($serverURL);
$parentURL = $parentURL === '\\' || $parentURL === '.' ? '/' : $parentURL;
$folderSlug = basename($serverURL);
$folderName = ucwords(str_replace(['-', '_'], ' ', $folderSlug));
$directory = __DIR__;
$items = scandir($directory) ?: [];
$folders = [];
$files = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..' || str_starts_with($item, '.')) {
continue;
}
$path = $directory . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$folders[] = $item;
continue;
}
if (is_file($path) && basename($item) !== 'index.php' && in_array(pathinfo($item, PATHINFO_EXTENSION), ['php', 'md'], true)) {
$files[] = pathinfo($item, PATHINFO_FILENAME);
}
}
natcasesort($folders);
natcasesort($files);
?>
<div class="flex justify-between items-center border-b border-slate-200 dark:border-slate-700 pb-5">
<div class="text-4xl font-bold"><?= e($folderName) ?></div>
<a class="btn btn-primary" href="<?= e($parentURL) ?>">
Back
</a>
</div>
<ul class="m-4">
<?php
foreach ($folders as $folder) :
$itemName = ucwords(str_replace(['-', '_'], ' ', $folder)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
<a class=' ms-2 hover:text-blue-600 font-bold' href='<?= e(rtrim($serverURL, '/') . '/' . $folder) ?>'>
<?= e($itemName) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
<ul class="m-4">
<?php foreach ($files as $fileName) :
$title = ucwords(str_replace(['-', '_'], ' ', $fileName)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
</svg>
<a class='ms-1 hover:text-blue-600' href='<?= e(rtrim($serverURL, '/') . '/' . $fileName) ?>'><?= e($title) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,40 @@
<section>
<!-- Container -->
<div class="mx-auto w-full max-w-7xl px-5 py-16 md:px-10 md:py-20">
<!-- Title -->
<h2 class="text-center mb-16 text-3xl font-bold md:text-5xl">
The latest and greatest news
</h2>
<!-- Content -->
<div class="mx-auto grid max-w-xl gap-5">
<a href="javascript:void(0);" class="flex flex-col items-center pb-8 text-center border-b border-gray-300 sm:flex-row sm:text-left">
<img src="<?= image(640,480) ?>" alt="" class="max-w-40 rounded-xl shadow-2xl" />
<div class="px-8">
<p class="mb-6 text-sm font-bold sm:text-base lg:mb-8">
Lorem ipsum dolor sit amet, consectetur adipiscing elit ut
</p>
<p class="text-sm text-gray-500">November 12, 2022</p>
</div>
</a>
<a href="javascript:void(0);" class="flex flex-col items-center pb-8 text-center border-b border-gray-300 sm:flex-row sm:text-left">
<img src="<?= image(640,480) ?>" alt="" class="max-w-40 rounded-xl shadow-2xl" />
<div class="px-8">
<p class="mb-6 text-sm font-bold sm:text-base lg:mb-8">
Lorem ipsum dolor sit amet, consectetur adipiscing elit ut
</p>
<p class="text-sm text-gray-500">November 12, 2022</p>
</div>
</a>
<a href="javascript:void(0);" class="flex flex-col items-center pb-8 text-center border-b border-gray-300 sm:flex-row sm:text-left">
<img src="<?= image(640,480) ?>" alt="" class="max-w-40 rounded-xl shadow-2xl" />
<div class="px-8">
<p class="mb-6 text-sm font-bold sm:text-base lg:mb-8">
Lorem ipsum dolor sit amet, consectetur adipiscing elit ut
</p>
<p class="text-sm text-gray-500">November 12, 2022</p>
</div>
</a>
</div>
</div>
</section>

View File

@@ -0,0 +1,67 @@
<section>
<!-- Container -->
<div class="mx-auto w-full max-w-7xl px-5 py-16 md:px-10 md:py-20">
<!-- Component -->
<div class="flex flex-col items-center">
<h2 class="text-center text-3xl font-bold md:text-5xl">
The latest and greatest news
</h2>
<p class="mb-8 mt-4 text-center text-sm text-gray-500 sm:text-base md:mb-12 lg:mb-16">
Lorem ipsum dolor sit amet elit ut aliquam
</p>
<!-- Content -->
<div class="mb-8 grid gap-5 sm:grid-cols-2 sm:justify-items-stretch md:mb-12 md:grid-cols-3 lg:mb-16 lg:gap-6">
<!-- Item -->
<a href="javascript:void(0);" class="flex flex-col gap-4 bg-white rounded-md shadow px-4 py-8 md:p-0">
<img src="<?= image() ?>" alt="" class="rounded-t-lg object-cover" />
<div class="flex flex-col items-start pb-5 px-4">
<p class="mb-4 text-xl font-bold md:text-2xl">
The latest news with Flowspark
</p>
<div class="flex flex-col items-start text-sm text-gray-500 lg:flex-row lg:items-center">
<p>Laila Bahar</p>
<p class="mx-2 hidden lg:block">-</p>
<p>6 mins read</p>
</div>
</div>
</a>
<!-- Item -->
<a href="javascript:void(0);" class="flex flex-col gap-4 bg-white rounded-md shadow px-4 py-8 md:p-0">
<img src="<?= image() ?>" alt="" class="rounded-t-lg object-cover" />
<div class="flex flex-col items-start pb-5 px-4">
<p class="mb-4 text-xl font-bold md:text-2xl">
The latest news with Flowspark
</p>
<div class="flex flex-col items-start text-sm text-gray-500 lg:flex-row lg:items-center">
<p>Laila Bahar</p>
<p class="mx-2 hidden lg:block">-</p>
<p>6 mins read</p>
</div>
</div>
</a>
<!-- Item -->
<a href="javascript:void(0);" class="flex flex-col gap-4 bg-white rounded-md shadow px-4 py-8 md:p-0">
<img src="<?= image() ?>" alt="" class="rounded-t-lg object-cover" />
<div class="flex flex-col items-start pb-5 px-4">
<p class="mb-4 text-xl font-bold md:text-2xl">
The latest news with Flowspark
</p>
<div class="flex flex-col items-start text-sm text-gray-500 lg:flex-row lg:items-center">
<p>Laila Bahar</p>
<p class="mx-2 hidden lg:block">-</p>
<p>6 mins read</p>
</div>
</div>
</a>
</div>
<!-- Button -->
<a href="javascript:void(0);" class="rounded-md bg-black px-6 py-3 text-center font-semibold text-white">
View More
</a>
</div>
</div>
</section>

View File

@@ -0,0 +1,96 @@
<section>
<!-- Container -->
<div class="band">
<!-- Title -->
<h2 class="text-center text-3xl font-bold md:text-5xl md:text-left">
The latest and greatest news
</h2>
<p class="mb-8 mt-4 text-center text-sm text-gray-500 sm:text-base md:mb-12 lg:mb-16 md:text-left">
Lorem ipsum dolor sit amet elit ut aliquam
</p>
<!-- Content -->
<div class="mx-auto grid gap-8 md:grid-cols-2">
<a href="javascript:void(0);" class="flex flex-col gap-4 rounded-md [grid-area:1/1/4/2] c-grid-area-1_1_4_2 md:pr-8">
<img src="<?= image() ?>" alt="" class="inline-block h-72 w-full rounded object-cover" />
<div class="flex flex-col items-start py-4">
<div class="mb-4 rounded-md bg-gray-100 dark:bg-slate-900 px-2 py-1.5">
<p class="text-sm eyebrow">
FramexEngine
</p>
</div>
<p class="mb-4 text-xl font-bold md:text-2xl">
The latest news with Flowspark
</p>
<div class="flex flex-col text-sm text-gray-500 md:flex-row">
<p>Laila Bahar</p>
<p class="mx-2 hidden md:block">-</p>
<p>6 mins read</p>
</div>
</div>
</a>
<div class="md:flex md:justify-between md:flex-col">
<a href="javascript:void(0);" class="flex flex-col pb-5 md:mb-3 md:flex-row md:border-b md:border-gray-300">
<img src="<?= image() ?>" alt="" class="inline-block h-60 w-full rounded object-cover md:h-32 md:w-32" />
<div class="flex flex-col items-start pt-4 md:px-8">
<div class="mb-2 rounded-md bg-gray-100 dark:bg-slate-900 px-2 py-1.5">
<p class="text-sm eyebrow">
FramexEngine
</p>
</div>
<p class="mb-2 text-sm font-bold sm:text-base">
Here is the title for this blog
</p>
<div class="flex flex-col items-start">
<div class="flex flex-col text-sm text-gray-500 sm:text-base md:flex-row md:items-center">
<p>Laila Bahar</p>
<p class="mx-2 hidden md:block">-</p>
<p>6 mins read</p>
</div>
</div>
</div>
</a>
<a href="javascript:void(0);" class="flex flex-col pb-5 md:mb-3 md:flex-row md:border-b md:border-gray-300">
<img src="<?= image() ?>" alt="" class="inline-block h-60 w-full rounded object-cover md:h-32 md:w-32" />
<div class="flex flex-col items-start pt-4 md:px-8">
<div class="mb-2 rounded-md bg-gray-100 dark:bg-slate-900 px-2 py-1.5">
<p class="text-sm eyebrow">
FramexEngine
</p>
</div>
<p class="mb-2 text-sm font-bold sm:text-base">
Here is the title for this blog
</p>
<div class="flex flex-col items-start">
<div class="flex flex-col text-sm text-gray-500 sm:text-base md:flex-row md:items-center">
<p>Laila Bahar</p>
<p class="mx-2 hidden md:block">-</p>
<p>6 mins read</p>
</div>
</div>
</div>
</a>
<a href="javascript:void(0);" class="flex flex-col pb-5 md:mb-3 md:flex-row md:border-b md:border-gray-300">
<img src="<?= image() ?>" alt="" class="inline-block h-60 w-full rounded object-cover md:h-32 md:w-32" />
<div class="flex flex-col items-start pt-4 md:px-8">
<div class="mb-2 rounded-md bg-gray-100 dark:bg-slate-900 px-2 py-1.5">
<p class="text-sm eyebrow">
FramexEngine
</p>
</div>
<p class="mb-2 text-sm font-bold sm:text-base">
Here is the title for this blog
</p>
<div class="flex flex-col items-start">
<div class="flex flex-col text-sm text-gray-500 sm:text-base md:flex-row md:items-center">
<p>Laila Bahar</p>
<p class="mx-2 hidden md:block">-</p>
<p>6 mins read</p>
</div>
</div>
</div>
</a>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,155 @@
<section>
<!-- Container -->
<div class="mx-auto w-full max-w-7xl px-5 py-16 md:px-10 md:py-20">
<div class="text-center mb-12">
<!-- Title -->
<h2 class="mb-4 mt-6 text-3xl font-bold md:text-5xl">
The latest and greatest news
</h2>
<p class="text-gray-500 mt-2">
Lorem ipsum dolor sit amet elit ut aliquam
</p>
<!-- Buttons -->
<div class="my-10 md:my-20 flex flex-col md:flex-row justify-center gap-3">
<button class="px-4 py-2 bg-gray-100 font-semibold rounded-full">
Engaging Articles
</button>
<button class="px-4 py-2 bg-black text-white font-semibold rounded-full">
Product Updates
</button>
<button class="px-4 py-2 bg-gray-100 font-semibold rounded-full">
Reflex Workflows
</button>
<button class="px-4 py-2 bg-gray-100 font-semibold rounded-full">
Artificial Intelligence
</button>
</div>
</div>
<!-- Blog Content -->
<div class="max-w-6xl mx-auto">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<!-- Blog Item -->
<div class=" bg-gray-50 rounded-lg overflow-hidden">
<div class="relative h-80">
<img class="h-80 w-full object-cover" src="<?= image() ?>" alt="" />
<a class="absolute bottom-5 right-5 btn btn-secondary ">
Product Updates
</a>
</div>
<div class="p-4 flex justify-between items-center">
<div>
<h2 class="text-lg font-semibold mt-2">
Here is the title for this News
</h2>
<p>Lorem ipsum dolor sit amet elit ut aliquam</p>
</div>
<button class="cursor-pointer h-14 w-14">
<svg class="h-14 w-14" width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M30.0001 52.5001C42.4265 52.5001 52.5001 42.4265 52.5001 30.0001C52.5001 17.5736 42.4265 7.5 30.0001 7.5C17.5736 7.5 7.5 17.5736 7.5 30.0001C7.5 42.4265 17.5736 52.5001 30.0001 52.5001Z" fill="black" stroke="black" stroke-width="2" stroke-miterlimit="10" />
<path d="M31.4297 37.9454L39.375 30L31.4297 22.0547" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M20.625 30H39.3751" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
</div>
<!-- Blog Item -->
<div class="relative bg-gray-50 rounded-lg overflow-hidden">
<div class="relative h-80">
<img class="h-80 w-full object-cover" src="<?= image() ?>" alt="" />
<a class="absolute bottom-5 right-5 btn btn-secondary ">
Product Updates
</a>
</div>
<div class="p-4 flex justify-between items-center">
<div>
<h2 class="text-lg font-semibold mt-2">
Here is the title for this News
</h2>
<p>Lorem ipsum dolor sit amet elit ut aliquam</p>
</div>
<button class="cursor-pointer h-14 w-14">
<svg class="h-14 w-14" width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M30.0001 52.5001C42.4265 52.5001 52.5001 42.4265 52.5001 30.0001C52.5001 17.5736 42.4265 7.5 30.0001 7.5C17.5736 7.5 7.5 17.5736 7.5 30.0001C7.5 42.4265 17.5736 52.5001 30.0001 52.5001Z" fill="black" stroke="black" stroke-width="2" stroke-miterlimit="10" />
<path d="M31.4297 37.9454L39.375 30L31.4297 22.0547" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<path d="M20.625 30H39.3751" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</button>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Blog Item -->
<div class="bg-gray-50 rounded-lg overflow-hidden">
<div class="relative h-72">
<img class="h-72 w-full object-cover" src="<?= image() ?>" alt="" />
<a class="absolute bottom-5 right-5 btn btn-secondary ">
Product Updates
</a>
</div>
<div class="p-4">
<h2 class="text-lg font-semibold mt-2">
Here is the title for this News
</h2>
<p class="text-gray-500">
We make every expression of Hero Spirits with precision and
passion
</p>
</div>
</div>
<!-- Blog Item -->
<div class=" bg-gray-50 rounded-lg overflow-hidden">
<div class="relative h-72">
<img class="h-72 w-full object-cover" src="<?= image() ?>" alt="" />
<a class="absolute bottom-5 right-5 btn btn-secondary ">
Product Updates
</a>
</div>
<div class="p-4">
<h2 class="text-lg font-semibold mt-2">
Here is the title for this News
</h2>
<p class="text-gray-500">
We make every expression of Hero Spirits with precision and
passion
</p>
</div>
</div>
<!-- Blog Item -->
<div class="bg-gray-50 rounded-lg overflow-hidden">
<div class="relative h-72">
<img class="h-72 w-full object-cover" src="<?= image() ?>" alt="" />
<a class="absolute bottom-5 right-5 btn btn-secondary ">
Product Updates
</a>
</div>
<div class="p-4">
<h2 class="text-lg font-semibold mt-2">
Here is the title for this News
</h2>
<p class="text-gray-500">
We make every expression of Hero Spirits with precision and
passion
</p>
</div>
</div>
</div>
<!-- Button -->
<div class="mt-10 md:mt-20 text-center">
<button class="px-6 py-2 bg-black text-white rounded-lg">
Load More
</button>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,77 @@
<section class="section">
<div class="band">
<div class="card">
<div class="entry-section">
<?php
$serverURL = rtrim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), '/') ?: '/';
$parentURL = $serverURL === '/' ? '/' : dirname($serverURL);
$parentURL = $parentURL === '\\' || $parentURL === '.' ? '/' : $parentURL;
$folderSlug = basename($serverURL);
$folderName = ucwords(str_replace(['-', '_'], ' ', $folderSlug));
$directory = __DIR__;
$items = scandir($directory) ?: [];
$folders = [];
$files = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..' || str_starts_with($item, '.')) {
continue;
}
$path = $directory . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$folders[] = $item;
continue;
}
if (is_file($path) && basename($item) !== 'index.php' && in_array(pathinfo($item, PATHINFO_EXTENSION), ['php', 'md'], true)) {
$files[] = pathinfo($item, PATHINFO_FILENAME);
}
}
natcasesort($folders);
natcasesort($files);
?>
<div class="flex justify-between items-center border-b border-slate-200 dark:border-slate-700 pb-5">
<div class="text-4xl font-bold"><?= e($folderName) ?></div>
<a class="btn btn-primary" href="<?= e($parentURL) ?>">
Back
</a>
</div>
<ul class="m-4">
<?php
foreach ($folders as $folder) :
$itemName = ucwords(str_replace(['-', '_'], ' ', $folder)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
<a class=' ms-2 hover:text-blue-600 font-bold' href='<?= e(rtrim($serverURL, '/') . '/' . $folder) ?>'>
<?= e($itemName) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
<ul class="m-4">
<?php foreach ($files as $fileName) :
$title = ucwords(str_replace(['-', '_'], ' ', $fileName)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
</svg>
<a class='ms-1 hover:text-blue-600' href='<?= e(rtrim($serverURL, '/') . '/' . $fileName) ?>'><?= e($title) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,26 @@
<section class="bg-white dark:bg-slate-900">
<div class="mx-auto w-screen max-w-7xl px-4 py-16 sm:px-6 sm:py-24 lg:px-8 lg:py-32">
<div class="mx-auto max-w-prose text-center">
<h1 class="text-4xl font-bold text-gray-900 dark:text-white/90 sm:text-5xl">
Understand user flow and
<strong class="text-indigo-600"> increase </strong>
conversions
</h1>
<p class="mt-4 text-base text-pretty text-gray-700 dark:text-white/60 sm:text-lg/relaxed">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque, nisi. Natus, provident
accusamus impedit minima harum corporis iusto.
</p>
<div class="mt-4 flex justify-center gap-4 sm:mt-6">
<a class="btn btn-primary" href="#">
Get Started
</a>
<a class="btn btn-secondary" href="#">
Learn More
</a>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,33 @@
<section class="section">
<!-- Container -->
<div class="band">
<!-- Title -->
<h2 class="mb-8 text-3xl font-bold md:text-5xl md:mb-10">
Meet Flowspark
</h2>
<p class="mb-8 max-w-lg text-sm text-gray-500 sm:text-base md:mb-16">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
varius enim in eros elementum tristique. Duis cursus, mi quis viverra
ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat.
</p>
<div class="grid gap-8 lg:grid-cols-2 lg:gap-10">
<img src="<?= image() ?>image" alt="" class="inline-block h-full w-full rounded-2xl object-cover" />
<div class="flex flex-col gap-5 rounded-2xl border border-solid border-black p-10 sm:p-12">
<h2 class="text-3xl font-bold md:text-5xl">Our Mission</h2>
<p class="text-sm text-gray-500 sm:text-base">
Aliquet risus feugiat in ante metus. Arcu dui vivamus arcu felis
bibendum ut. Vestibulum lorem sed risus ultricies tristique nulla.
Vitae et leo duis ut diam quam. Bibendum arcu vitae elementum
curabitur vitae nunc. Dictumst vestibulum rhoncus est
pellentesque. Lectus proin nibh nisl condimentum id. Ullamcorper
dignissim cras tincidunt lobortis feugiat vivamus.
<br />
<br />
Massa id neque aliquam vestibulum morbi blandit. Nulla
pellentesque dignissim enim sit amet venenatis.
</p>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,77 @@
<section class="section">
<div class="band">
<div class="card">
<div class="entry-section">
<?php
$serverURL = rtrim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), '/') ?: '/';
$parentURL = $serverURL === '/' ? '/' : dirname($serverURL);
$parentURL = $parentURL === '\\' || $parentURL === '.' ? '/' : $parentURL;
$folderSlug = basename($serverURL);
$folderName = ucwords(str_replace(['-', '_'], ' ', $folderSlug));
$directory = __DIR__;
$items = scandir($directory) ?: [];
$folders = [];
$files = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..' || str_starts_with($item, '.')) {
continue;
}
$path = $directory . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$folders[] = $item;
continue;
}
if (is_file($path) && basename($item) !== 'index.php' && in_array(pathinfo($item, PATHINFO_EXTENSION), ['php', 'md'], true)) {
$files[] = pathinfo($item, PATHINFO_FILENAME);
}
}
natcasesort($folders);
natcasesort($files);
?>
<div class="flex justify-between items-center border-b border-slate-200 dark:border-slate-700 pb-5">
<div class="text-4xl font-bold"><?= e($folderName) ?></div>
<a class="btn btn-primary" href="<?= e($parentURL) ?>">
Back
</a>
</div>
<ul class="m-4">
<?php
foreach ($folders as $folder) :
$itemName = ucwords(str_replace(['-', '_'], ' ', $folder)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
<a class=' ms-2 hover:text-blue-600 font-bold' href='<?= e(rtrim($serverURL, '/') . '/' . $folder) ?>'>
<?= e($itemName) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
<ul class="m-4">
<?php foreach ($files as $fileName) :
$title = ucwords(str_replace(['-', '_'], ' ', $fileName)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
</svg>
<a class='ms-1 hover:text-blue-600' href='<?= e(rtrim($serverURL, '/') . '/' . $fileName) ?>'><?= e($title) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,77 @@
<section class="section">
<div class="band">
<div class="card">
<div class="entry-section">
<?php
$serverURL = rtrim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), '/') ?: '/';
$parentURL = $serverURL === '/' ? '/' : dirname($serverURL);
$parentURL = $parentURL === '\\' || $parentURL === '.' ? '/' : $parentURL;
$folderSlug = basename($serverURL);
$folderName = ucwords(str_replace(['-', '_'], ' ', $folderSlug));
$directory = __DIR__;
$items = scandir($directory) ?: [];
$folders = [];
$files = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..' || str_starts_with($item, '.')) {
continue;
}
$path = $directory . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$folders[] = $item;
continue;
}
if (is_file($path) && basename($item) !== 'index.php' && in_array(pathinfo($item, PATHINFO_EXTENSION), ['php', 'md'], true)) {
$files[] = pathinfo($item, PATHINFO_FILENAME);
}
}
natcasesort($folders);
natcasesort($files);
?>
<div class="flex justify-between items-center border-b border-slate-200 dark:border-slate-700 pb-5">
<div class="text-4xl font-bold"><?= e($folderName) ?></div>
<a class="btn btn-primary" href="<?= e($parentURL) ?>">
Back
</a>
</div>
<ul class="m-4">
<?php
foreach ($folders as $folder) :
$itemName = ucwords(str_replace(['-', '_'], ' ', $folder)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
<a class=' ms-2 hover:text-blue-600 font-bold' href='<?= e(rtrim($serverURL, '/') . '/' . $folder) ?>'>
<?= e($itemName) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
<ul class="m-4">
<?php foreach ($files as $fileName) :
$title = ucwords(str_replace(['-', '_'], ' ', $fileName)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
</svg>
<a class='ms-1 hover:text-blue-600' href='<?= e(rtrim($serverURL, '/') . '/' . $fileName) ?>'><?= e($title) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,77 @@
<section class="section">
<div class="band">
<div class="card">
<div class="entry-section">
<?php
$serverURL = rtrim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), '/') ?: '/';
$parentURL = $serverURL === '/' ? '/' : dirname($serverURL);
$parentURL = $parentURL === '\\' || $parentURL === '.' ? '/' : $parentURL;
$folderSlug = basename($serverURL);
$folderName = ucwords(str_replace(['-', '_'], ' ', $folderSlug));
$directory = __DIR__;
$items = scandir($directory) ?: [];
$folders = [];
$files = [];
foreach ($items as $item) {
if ($item === '.' || $item === '..' || str_starts_with($item, '.')) {
continue;
}
$path = $directory . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
$folders[] = $item;
continue;
}
if (is_file($path) && basename($item) !== 'index.php' && in_array(pathinfo($item, PATHINFO_EXTENSION), ['php', 'md'], true)) {
$files[] = pathinfo($item, PATHINFO_FILENAME);
}
}
natcasesort($folders);
natcasesort($files);
?>
<div class="flex justify-between items-center border-b border-slate-200 dark:border-slate-700 pb-5">
<div class="text-4xl font-bold"><?= e($folderName) ?></div>
<a class="btn btn-primary" href="<?= e($parentURL) ?>">
Back
</a>
</div>
<ul class="m-4">
<?php
foreach ($folders as $folder) :
$itemName = ucwords(str_replace(['-', '_'], ' ', $folder)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
</svg>
<a class=' ms-2 hover:text-blue-600 font-bold' href='<?= e(rtrim($serverURL, '/') . '/' . $folder) ?>'>
<?= e($itemName) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
<ul class="m-4">
<?php foreach ($files as $fileName) :
$title = ucwords(str_replace(['-', '_'], ' ', $fileName)); ?>
<li class="mt-2">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path fill-rule="evenodd" d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z" clip-rule="evenodd" />
</svg>
<a class='ms-1 hover:text-blue-600' href='<?= e(rtrim($serverURL, '/') . '/' . $fileName) ?>'><?= e($title) ?></a>
</div>
</li>
<?php endforeach ?>
</ul>
</div>
</div>
</div>
</section>

View File

@@ -0,0 +1,20 @@
<section class="section">
<div class="band">
<div class="space-y-4 md:space-y-8">
<div class="max-w-prose">
<h2 class="text-2xl font-semibold text-gray-900 dark:text-white/90 sm:text-3xl">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
</h2>
<p class="mt-4 text-gray-700 dark:text-white/60">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Tenetur doloremque saepe
architecto maiores repudiandae amet perferendis repellendus, reprehenderit voluptas
sequi.
</p>
</div>
<img src="<?= image(1140, 600) ?>" class="rounded shadow-2xl" alt="">
</div>
</div>
</section>

View File

@@ -0,0 +1,88 @@
<?php
$data['title'] = 'Blog List Demo - Framex';
$data['metaDescription'] = 'A modern blog listing page demo built as a PHP view in Framex.';
$posts = [
[
'title' => 'Designing fast content systems with plain PHP',
'category' => 'Architecture',
'date' => 'May 12, 2026',
'read' => '7 min read',
'description' => 'A practical look at keeping site structure simple while still giving editors polished pages.',
'color' => 'bg-pre-blue',
],
[
'title' => 'Markdown pages that look finished by default',
'category' => 'Content',
'date' => 'May 8, 2026',
'read' => '5 min read',
'description' => 'How automatic prose styling lets documentation and articles ship without custom templates.',
'color' => 'bg-pre-emerald',
],
[
'title' => 'A small Tailwind design system for real websites',
'category' => 'Design',
'date' => 'May 4, 2026',
'read' => '6 min read',
'description' => 'Reusable sections, cards, buttons, and preset colors keep page building consistent.',
'color' => 'bg-pre-amber',
],
[
'title' => 'Dark mode without making every page complicated',
'category' => 'Frontend',
'date' => 'April 28, 2026',
'read' => '4 min read',
'description' => 'Persisted preferences, system fallback, and theme-aware utility classes in one flow.',
'color' => 'bg-pre-rose',
],
];
?>
<main>
<section class="section">
<div class="contain">
<div class="grid gap-10 lg:grid-cols-[0.85fr_1.15fr] lg:items-end">
<div>
<p class="eyebrow">Blog list demo</p>
<h1 class="mt-4 text-4xl font-semibold leading-tight text-slate-950 sm:text-6xl dark:text-white">
Editorial cards for a modern content index.
</h1>
</div>
<p class="text-lg leading-8 text-slate-600 dark:text-slate-400">
This page is a PHP view. It uses an array of posts, loops through the data, and renders a responsive grid without needing a separate component system.
</p>
</div>
<div class="mt-10 flex flex-wrap gap-3">
<span class="btn btn-secondary">All posts</span>
<span class="btn btn-ghost">Architecture</span>
<span class="btn btn-ghost">Content</span>
<span class="btn btn-ghost">Design</span>
</div>
<div class="mt-10 grid gap-5 md:grid-cols-2">
<?php foreach ($posts as $index => $post): ?>
<article class="card overflow-hidden p-0 <?= $index === 0 ? 'md:col-span-2' : '' ?>">
<div class="<?= e($post['color']) ?> p-6">
<div class="flex flex-wrap items-center gap-3 text-sm">
<span class="font-semibold"><?= e($post['category']) ?></span>
<span class="opacity-60">/</span>
<span class="opacity-80"><?= e($post['date']) ?></span>
</div>
<h2 class="mt-8 max-w-3xl text-2xl font-semibold leading-tight <?= $index === 0 ? 'sm:text-4xl' : '' ?>">
<?= e($post['title']) ?>
</h2>
</div>
<div class="p-6">
<p class="text-sm leading-6 text-slate-600 dark:text-slate-400"><?= e($post['description']) ?></p>
<div class="mt-6 flex items-center justify-between gap-4">
<span class="text-sm text-slate-500 dark:text-slate-400"><?= e($post['read']) ?></span>
<a class="btn btn-secondary" href="/demo/blog-post">Read post</a>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
</main>

View File

@@ -0,0 +1,98 @@
<?php
$data['title'] = 'Blog Post Demo - Framex';
$data['metaDescription'] = 'A modern blog post page demo built as a PHP view in Framex.';
?>
<main>
<article>
<header class="section">
<div class="contain">
<div class="mx-auto max-w-4xl text-center">
<p class="eyebrow">Architecture / 7 min read</p>
<h1 class="mt-5 text-4xl font-bold tracking-tight text-slate-950 sm:text-6xl dark:text-white">
Designing fast content systems with plain PHP.
</h1>
<p class="mt-6 text-lg leading-8 text-slate-600 dark:text-slate-400">
A modern site does not need a heavy runtime to feel polished. Framex keeps page resolution, templates, assets, and Markdown close to the filesystem.
</p>
<div class="mt-8 flex items-center justify-center gap-4 text-sm text-slate-500 dark:text-slate-400">
<span class="grid size-11 place-items-center rounded-full bg-blue-600 font-semibold text-white">Fx</span>
<div class="text-left">
<p class="font-semibold text-slate-900 dark:text-white">Framex Team</p>
<p>Published May 12, 2026</p>
</div>
</div>
</div>
</div>
</header>
<section class="contain">
<div class="bg-pre-blue rounded-lg p-8 md:p-12">
<div class="grid gap-6 md:grid-cols-3">
<div>
<p class="text-sm font-semibold uppercase tracking-widest opacity-70">Routing</p>
<p class="mt-3 text-2xl font-semibold">Files become URLs</p>
</div>
<div>
<p class="text-sm font-semibold uppercase tracking-widest opacity-70">Views</p>
<p class="mt-3 text-2xl font-semibold">PHP and Markdown</p>
</div>
<div>
<p class="text-sm font-semibold uppercase tracking-widest opacity-70">Design</p>
<p class="mt-3 text-2xl font-semibold">Tailwind CSS 4</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="contain grid gap-10 lg:grid-cols-[0.75fr_1.25fr]">
<aside class="lg:sticky lg:top-24 lg:self-start">
<div class="card">
<p class="text-sm font-semibold text-slate-950 dark:text-white">In this article</p>
<div class="mt-4 grid gap-3 text-sm text-slate-600 dark:text-slate-400">
<a href="#simple-routing" class="hover:text-blue-600 dark:hover:text-blue-400">Simple routing</a>
<a href="#shared-layouts" class="hover:text-blue-600 dark:hover:text-blue-400">Shared layouts</a>
<a href="#content-speed" class="hover:text-blue-600 dark:hover:text-blue-400">Content speed</a>
</div>
</div>
</aside>
<div class="prose">
<h2 id="simple-routing">Simple routing</h2>
<p>Framex resolves URLs by checking for matching PHP and Markdown files inside <code>app/views</code>. This keeps page creation predictable for new developers and small teams.</p>
<p>A route such as <code>/demo/blog-post</code> can be backed by <code>app/views/demo/blog-post.php</code>, while documentation can be written as Markdown in <code>app/views/docs/index.md</code>.</p>
<h2 id="shared-layouts">Shared layouts</h2>
<p>Every page is wrapped by the main template, which includes the top menu, current view content, footer, compiled CSS, and JavaScript. PHP views can set metadata through the <code>$data</code> array.</p>
<blockquote>
<p>Keep page-specific content in views, shared structure in templates, and reusable visual rules in Tailwind source CSS.</p>
</blockquote>
<h2 id="content-speed">Content speed</h2>
<p>Markdown pages are useful for documentation and simple publishing workflows. PHP views are better when a page needs custom data, cards, grids, forms, or conditional rendering.</p>
<pre><code>// PHP view route
app/views/demo/blog-post.php
// Markdown view route
app/views/demo/page.md</code></pre>
</div>
</div>
</section>
</article>
<section class="section-tight border-t border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900/40">
<div class="contain">
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<p class="eyebrow">Next demo</p>
<h2 class="mt-2 text-2xl font-semibold text-slate-950 dark:text-white">Explore the Markdown page.</h2>
</div>
<a class="btn btn-primary" href="/demo/page">Open Markdown demo</a>
</div>
</div>
</section>
</main>

86
app/views/demo/index.php Normal file
View File

@@ -0,0 +1,86 @@
<?php
$data['title'] = 'Framex Demo Pages';
$data['metaDescription'] = 'Explore modern demo pages built with Framex PHP views, Markdown views, Tailwind CSS, and reusable UI classes.';
$data['bodyClass'] = 'bg-slate-50 text-slate-950 dark:bg-slate-950 dark:text-slate-100';
$demoPages = [
[
'title' => 'Blog List',
'description' => 'A polished editorial index with featured content, filters, and responsive cards.',
'href' => '/demo/blog-list',
'label' => 'PHP view',
'color' => 'bg-pre-blue',
],
[
'title' => 'Blog Post',
'description' => 'A long-form article layout with author metadata, hero content, and related cards.',
'href' => '/demo/blog-post',
'label' => 'PHP view',
'color' => 'bg-pre-emerald',
],
[
'title' => 'Markdown Page',
'description' => 'A content page rendered from Markdown and automatically styled by `.prose`.',
'href' => '/demo/page',
'label' => 'MD view',
'color' => 'bg-pre-amber',
],
];
?>
<main>
<section class="section">
<div class="contain">
<div class="max-w-3xl">
<p class="eyebrow">Demo library</p>
<h1 class="mt-4 text-4xl font-bold text-gradient sm:text-6xl dark:text-white">
Modern pages built with the Framex view system.
</h1>
<p class="mt-6 text-lg leading-8 text-slate-600 dark:text-slate-400">
These demos show how PHP views and Markdown views can share the same template, navigation, footer, light/dark mode, and reusable Tailwind classes.
</p>
</div>
<div class="mt-12 grid gap-5 md:grid-cols-3">
<?php foreach ($demoPages as $page): ?>
<a class="card group flex min-h-72 flex-col justify-between overflow-hidden p-0" href="<?= e($page['href']) ?>">
<div class="<?= e($page['color']) ?> p-6">
<span class="inline-flex rounded-md bg-white/70 px-3 py-1 text-xs font-semibold text-slate-700 ring-1 ring-black/5 dark:bg-black/20 dark:text-white dark:ring-white/10">
<?= e($page['label']) ?>
</span>
<h2 class="mt-8 text-2xl font-bold text-current"><?= e($page['title']) ?></h2>
</div>
<div class="p-6">
<p class="text-sm leading-6 text-slate-600 dark:text-slate-400"><?= e($page['description']) ?></p>
<div class="mt-6 inline-flex items-center gap-2 text-sm font-semibold text-blue-600 group-hover:text-blue-500 dark:text-blue-400">
Open demo
<span aria-hidden="true">-></span>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
</div>
</section>
<section class="section-tight border-y border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900/40">
<div class="contain grid gap-5 md:grid-cols-4">
<div class="card">
<p class="text-3xl font-semibold text-slate-950 dark:text-white">4</p>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Demo routes</p>
</div>
<div class="card">
<p class="text-3xl font-semibold text-slate-950 dark:text-white">2</p>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">View types</p>
</div>
<div class="card">
<p class="text-3xl font-semibold text-slate-950 dark:text-white">4</p>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Preset colors</p>
</div>
<div class="card">
<p class="text-3xl font-semibold text-slate-950 dark:text-white">1</p>
<p class="mt-2 text-sm text-slate-600 dark:text-slate-400">Shared layout</p>
</div>
</div>
</section>
</main>

54
app/views/demo/page.md Normal file
View File

@@ -0,0 +1,54 @@
# Markdown Page Demo
This page is rendered from `app/views/demo/page.md`. It uses the same main template, top menu, footer, CSS, JavaScript, and light/dark mode as PHP views.
Markdown pages are ideal for documentation, policies, articles, guides, landing copy drafts, and simple content pages.
## What gets styled automatically
Framex wraps Markdown output in `.prose prose-shell`, so common content elements are styled without extra markup:
- Headings
- Paragraphs
- Links
- Ordered and unordered lists
- Inline code
- Code blocks
- Blockquotes
- Tables
- Images
- Horizontal rules
## Example content
You can write ordinary Markdown:
```md
## Feature section
- Fast routes
- PHP views
- Markdown views
- Tailwind styling
```
And it becomes a fully styled content page.
## Table example
| View type | Best for | Route example |
| --- | --- | --- |
| PHP | Dynamic layouts, cards, forms | `/demo/blog-list` |
| Markdown | Docs, articles, simple pages | `/demo/page` |
## Blockquote example
> Markdown is the fastest way to add polished content when the page does not need custom PHP logic.
## Next steps
Open the other demo pages:
- [Demo index](/demo)
- [Blog list](/demo/blog-list)
- [Blog post](/demo/blog-post)

415
app/views/docs/index.md Normal file
View File

@@ -0,0 +1,415 @@
# Framex Engine Documentation
Framex is a small PHP engine for building fast websites with plain PHP views, Markdown pages, reusable templates, and Tailwind CSS 4. It is designed to stay simple: URLs map to files, templates wrap views, and assets live in `public`.
## Project Structure
The important folders are:
```text
app/views/ Page views for URLs
core/ Router, renderer, helpers, and configuration
templates/ Layout templates and partials
templates/partials/ Shared pieces such as top menu and footer
public/ Web root for CSS, JS, images, and index.php
public/css/src/ Tailwind source CSS
public/css/style.css Compiled production CSS
public/js/init.js Theme toggle and menu behavior
```
The web server should point to `public` as the document root.
## URL Routing
Framex resolves the browser URL to a file inside `app/views`.
For a request like:
```text
/about
```
Framex checks these files in order:
```text
app/views/about.php
app/views/about/index.php
app/views/about.md
app/views/about/index.md
```
The first file that exists is rendered. If none exists, Framex falls back to `templates/error404.php`.
Examples:
```text
/ -> app/views/index.php
/docs -> app/views/docs/index.md
/about -> app/views/about/index.md
/pricing -> app/views/pricing.php
/blog/hello -> app/views/blog/hello.php or app/views/blog/hello/index.md
```
Trailing slashes are normalized, so `/docs/` and `/docs` resolve the same way.
## PHP Views
Use PHP views when a page needs custom markup, variables, conditionals, loops, forms, or reusable PHP logic.
Create a file:
```text
app/views/pricing.php
```
Then visit:
```text
/pricing
```
A PHP view can set page metadata by assigning values to the `$data` array:
```php
<?php
$data['title'] = 'Pricing - Framex';
$data['metaDescription'] = 'Simple pricing page built with Framex.';
$data['bodyClass'] = 'bg-slate-50 text-slate-950 dark:bg-slate-950 dark:text-slate-100';
?>
<main class="section">
<div class="contain">
<h1 class="text-4xl font-semibold">Pricing</h1>
<p class="mt-4 text-slate-600 dark:text-slate-400">Choose the right plan.</p>
</div>
</main>
```
The rendered PHP view is injected into the main template through `<?= $view ?>`.
## Markdown Views
Use Markdown views for documentation, simple content pages, articles, and static text-heavy pages.
Create a file:
```text
app/views/guide/index.md
```
Then visit:
```text
/guide
```
Markdown files are parsed by `Parsedown` and automatically wrapped in:
```html
<article class="prose prose-shell">
...
</article>
```
That means Markdown pages automatically get readable typography, spacing, links, lists, code blocks, tables, blockquotes, images, and light/dark colors.
Example Markdown page:
```md
# My Guide
This page is rendered from Markdown.
## Features
- Clean URLs
- Automatic styling
- Light and dark mode
```php
echo 'Code blocks are styled too';
```
```
## Templates
The default layout is:
```text
templates/main.php
```
It handles:
- HTML document structure
- Meta tags
- CSS include
- Dark-mode bootstrap
- Top menu partial
- Current view output
- Footer partial
- JavaScript include
The main content flow is:
```php
<?= partial('topmenu') ?>
<?= $view ?>
<?= partial('footer') ?>
```
Partials live in:
```text
templates/partials/
```
For example:
```text
templates/partials/topmenu.php
templates/partials/footer.php
```
Load a partial with:
```php
<?= partial('topmenu') ?>
```
## Metadata
PHP views can customize metadata with `$data`.
Common keys:
```php
$data['title'] = 'Page title';
$data['metaDescription'] = 'Page description for search and sharing.';
$data['metaImage'] = image(1200, 630);
$data['bodyClass'] = 'custom body classes';
$data['template'] = 'main';
```
If a key is not set, `templates/main.php` uses sensible defaults.
Markdown views currently use the default metadata unless the renderer is extended to read front matter.
## Assets
Public files are served from `public`.
Examples:
```text
public/css/style.css -> /css/style.css
public/js/init.js -> /js/init.js
public/images/logo.png -> /images/logo.png
```
Use the `asset()` helper for cache-busted URLs:
```php
<link rel="stylesheet" href="<?= asset('css/style.css') ?>">
<script src="<?= asset('js/init.js', ['prefix' => 'framex', 'hash' => true]) ?>"></script>
```
Useful asset options:
```php
asset('css/style.css');
asset('js/init.js', ['hash' => true]);
asset('images/logo.png', ['absolute' => true]);
asset('images/missing.png', ['fallback' => '/images/default.png']);
```
## Tailwind CSS
Framex uses Tailwind CSS 4 through the Tailwind CLI.
Install dependencies:
```bash
npm install
```
Build CSS:
```bash
npm run build:css
```
Watch CSS during development:
```bash
npm run watch:css
```
Source CSS:
```text
public/css/src/style.css
```
Compiled CSS:
```text
public/css/style.css
```
Tailwind scans these sources:
```css
@source "../../../templates/**/*.php";
@source "../../../app/**/*.php";
@source "../../../app/**/*.md";
```
When you add new Tailwind classes in PHP or Markdown files, rebuild the CSS.
## Reusable CSS Classes
The project includes reusable classes in `public/css/src/style.css`.
Layout:
```html
<section class="section">
<div class="contain">...</div>
</section>
```
Buttons:
```html
<a class="btn btn-primary" href="/docs">Primary</a>
<a class="btn btn-secondary" href="/about">Secondary</a>
<button class="btn btn-ghost">Ghost</button>
```
Cards:
```html
<article class="card">
<h2>Card title</h2>
<p>Card content.</p>
</article>
```
Preset backgrounds:
```html
<div class="bg-pre-blue">Blue preset</div>
<div class="bg-pre-emerald">Emerald preset</div>
<div class="bg-pre-amber">Amber preset</div>
<div class="bg-pre-rose">Rose preset</div>
```
These presets include both light and dark colors.
## Light and Dark Mode
Dark mode is controlled by the `dark` class on the `<html>` element.
The inline script in `templates/main.php` runs before the stylesheet loads. It checks:
1. The saved `framex-theme` value in `localStorage`.
2. The browser system preference.
3. Light mode as the fallback.
The toggle buttons in `templates/partials/topmenu.php` use:
```html
data-theme-toggle
```
The behavior is implemented in:
```text
public/js/init.js
```
Use Tailwind dark variants anywhere:
```html
<div class="bg-white text-slate-950 dark:bg-slate-900 dark:text-white">
Theme-aware content
</div>
```
## Running Locally
Install dependencies and build CSS:
```bash
npm install
npm run build:css
```
Start PHP's built-in server:
```bash
php -S localhost:8000 -t public
```
Open:
```text
http://localhost:8000/
```
Useful URLs:
```text
/ Landing page
/docs This documentation
/about Example Markdown page
```
## Adding a New Page
For a PHP page:
```text
app/views/services.php
```
Visit:
```text
/services
```
For a Markdown page:
```text
app/views/services/index.md
```
Visit:
```text
/services
```
For a nested page:
```text
app/views/blog/hello-world/index.md
```
Visit:
```text
/blog/hello-world
```
## Recommended Workflow
1. Add or edit a PHP or Markdown view in `app/views`.
2. Use `templates/partials` for shared UI.
3. Use `.section`, `.contain`, `.btn`, `.card`, and preset backgrounds for consistent design.
4. Run `npm run build:css` after changing Tailwind classes.
5. Test the URL in the browser.
Framex works best when pages stay close to the filesystem, shared layout stays in templates, and reusable styling stays in the Tailwind source CSS.

178
app/views/index.php Normal file
View File

@@ -0,0 +1,178 @@
<?php
$data['title'] = "Framex Engine — Build Static Sites at Lightspeed";
$data['metaDescription'] = "Framex is a lightweight PHP engine for building blazing-fast static sites. No database, no bloat, just pure performance.";
$data['bodyClass'] = "bg-slate-50 text-slate-950 dark:bg-slate-950 dark:text-slate-100";
?>
<main>
<section class="section relative overflow-hidden">
<div class="contain grid items-center gap-12 lg:grid-cols-[1.05fr_0.95fr]">
<div>
<p class="eyebrow">PHP engine plus Tailwind CSS 4</p>
<h1 class="mt-5 max-w-4xl text-5xl font-bold text-gradient tracking-tight sm:text-6xl lg:text-7xl dark:text-white/80">
Build static sites that feel fast before you even optimize them.
</h1>
<p class="mt-6 max-w-2xl text-lg leading-8 text-slate-600 dark:text-slate-400">
FramexEngine keeps routing, markdown, templates, and assets simple so you can ship lightweight PHP sites with a modern design system already in place.
</p>
<div id="start" class="mt-8 flex flex-col gap-3 sm:flex-row">
<a class="btn btn-primary" href="/docs">Read the docs</a>
<a class="btn btn-secondary" href="#features">Explore features</a>
</div>
</div>
<div class="card p-0">
<div class="border-b border-slate-200 px-4 py-3 dark:border-slate-800">
<div class="flex gap-2">
<span class="size-3 rounded-full bg-rose-400"></span>
<span class="size-3 rounded-full bg-amber-400"></span>
<span class="size-3 rounded-full bg-emerald-400"></span>
</div>
</div>
<div class="space-y-5 p-6">
<div class="rounded-lg bg-slate-950 p-5 font-mono text-sm leading-7 text-slate-200 dark:bg-black">
<p><span class="text-emerald-300">&lt;?php</span></p>
<p><span class="text-blue-300">$data['title']</span> = <span class="text-amber-200">'Framex'</span>;</p>
<p><span class="text-slate-500">// templates, markdown, Tailwind</span></p>
<p><span class="text-rose-300">?&gt;</span></p>
</div>
<div class="grid gap-3 sm:grid-cols-2">
<div class="bg-pre-blue rounded-lg p-4">
<p class="text-sm font-semibold">CSS-first</p>
<p class="mt-1 text-sm opacity-80">Tailwind v4 build flow.</p>
</div>
<div class="bg-pre-emerald rounded-lg p-4">
<p class="text-sm font-semibold">Markdown</p>
<p class="mt-1 text-sm opacity-80">Auto styled pages.</p>
</div>
</div>
</div>
</div>
</div>
</section>
<section id="features" class="section border-y border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900/40">
<div class="contain">
<div class="max-w-2xl">
<p class="eyebrow">Features</p>
<h2 class="mt-4 text-3xl font-semibold text-slate-950 sm:text-4xl dark:text-white">A compact foundation for real projects.</h2>
</div>
<div class="mt-10 grid gap-5 md:grid-cols-3">
<article class="card">
<h3 class="text-lg font-semibold text-slate-950 dark:text-white">Simple routing</h3>
<p class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">PHP and markdown views resolve cleanly from the app views directory.</p>
</article>
<article class="card">
<h3 class="text-lg font-semibold text-slate-950 dark:text-white">Reusable UI</h3>
<p class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">Sections, buttons, cards, containers, and preset backgrounds are ready to use.</p>
</article>
<article class="card">
<h3 class="text-lg font-semibold text-slate-950 dark:text-white">Dark mode</h3>
<p class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">Theme preference follows the system first, then persists your manual choice.</p>
</article>
</div>
</div>
</section>
<section id="ideas" class="section">
<div class="contain">
<div class="grid gap-10 lg:grid-cols-[0.8fr_1.2fr] lg:items-start">
<div>
<p class="eyebrow">FramexEngine ideas</p>
<h2 class="mt-4 text-3xl font-semibold text-slate-950 sm:text-4xl dark:text-white">Start small, then grow only where the project needs it.</h2>
<p class="mt-5 text-base leading-7 text-slate-600 dark:text-slate-400">
Framex works well for pages that should be easy to edit, fast to ship, and simple to understand from the filesystem.
</p>
<div class="mt-8">
<a class="btn btn-secondary" href="/demo">View demo pages</a>
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2">
<article class="card">
<div class="bg-pre-blue mb-5 inline-flex rounded-lg px-3 py-2 text-sm font-semibold">01</div>
<h3 class="text-lg font-semibold text-slate-950 dark:text-white">Documentation hub</h3>
<p class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">Use Markdown views for guides, API notes, changelogs, and internal knowledge bases.</p>
</article>
<article class="card">
<div class="bg-pre-emerald mb-5 inline-flex rounded-lg px-3 py-2 text-sm font-semibold">02</div>
<h3 class="text-lg font-semibold text-slate-950 dark:text-white">Client landing pages</h3>
<p class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">Build fast campaign pages with shared templates, reusable cards, and Tailwind utilities.</p>
</article>
<article class="card">
<div class="bg-pre-amber mb-5 inline-flex rounded-lg px-3 py-2 text-sm font-semibold">03</div>
<h3 class="text-lg font-semibold text-slate-950 dark:text-white">API dashboards</h3>
<p class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">Use PHP views for simple data pages, status panels, metrics, and external API summaries.</p>
</article>
<article class="card">
<div class="bg-pre-rose mb-5 inline-flex rounded-lg px-3 py-2 text-sm font-semibold">04</div>
<h3 class="text-lg font-semibold text-slate-950 dark:text-white">Personal publishing</h3>
<p class="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">Create a lightweight blog, portfolio, or notes site without adding a database first.</p>
</article>
</div>
</div>
</div>
</section>
<section id="markdown" class="section">
<div class="contain grid gap-10 lg:grid-cols-[0.9fr_1.1fr] lg:items-start">
<div>
<p class="eyebrow">Markdown pages</p>
<h2 class="mt-4 text-3xl font-semibold text-slate-950 sm:text-4xl dark:text-white">Drop in a `.md` file and get a designed page.</h2>
<p class="mt-5 text-base leading-7 text-slate-600 dark:text-slate-400">
Framex already parses markdown views. The refreshed `.prose` layer now styles content, code, images, tables, and blockquotes automatically in both themes.
</p>
</div>
<div class="card">
<article class="prose">
<h1>Markdown preview</h1>
<p>Write normal content and let the design system handle spacing, readable line length, and theme colors.</p>
<blockquote><p>Markdown pages should look intentional without extra template work.</p></blockquote>
<pre><code>## Fast content
- Create a view.md file
- Publish clean HTML
- Keep styling automatic</code></pre>
</article>
</div>
</div>
</section>
<section id="themes" class="section border-y border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900/40">
<div class="contain">
<div class="max-w-2xl">
<p class="eyebrow">Preset colors</p>
<h2 class="mt-4 text-3xl font-semibold text-slate-950 sm:text-4xl dark:text-white">Four reusable light and dark background helpers.</h2>
</div>
<div class="mt-10 grid gap-4 md:grid-cols-4">
<div class="bg-pre-blue rounded-lg p-5">
<p class="font-semibold">Blue</p>
<p class="mt-2 text-sm opacity-80">Primary calls and product highlights.</p>
</div>
<div class="bg-pre-emerald rounded-lg p-5">
<p class="font-semibold">Emerald</p>
<p class="mt-2 text-sm opacity-80">Success states and fresh sections.</p>
</div>
<div class="bg-pre-amber rounded-lg p-5">
<p class="font-semibold">Amber</p>
<p class="mt-2 text-sm opacity-80">Notes, warnings, and warm accents.</p>
</div>
<div class="bg-pre-rose rounded-lg p-5">
<p class="font-semibold">Rose</p>
<p class="mt-2 text-sm opacity-80">Promos and energetic content blocks.</p>
</div>
</div>
</div>
</section>
<section class="section">
<div class="contain">
<div class="rounded-lg bg-slate-950 px-6 py-12 text-center text-white shadow-xl shadow-slate-950/20 dark:bg-white dark:text-slate-950">
<p class="text-sm font-semibold uppercase tracking-widest text-blue-300 dark:text-blue-600">Ready foundation</p>
<h2 class="mx-auto mt-4 max-w-2xl text-3xl font-semibold sm:text-4xl">Start with structure, styling, and markdown already connected.</h2>
<div class="mt-8 flex justify-center">
<a class="btn bg-white text-slate-950 hover:bg-slate-100 dark:bg-slate-950 dark:text-white dark:hover:bg-slate-900" href="/docs">Open documentation</a>
</div>
</div>
</div>
</section>
</main>

11
app/views/lab.php Normal file
View File

@@ -0,0 +1,11 @@
<div class="section bg-amber-100">
<div class="contain">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui excepturi soluta quos nostrum, est ex, maxime repudiandae laborum molestias inventore debitis. Nihil quidem explicabo assumenda debitis quas, beatae molestiae officiis.</p>
</div>
</div>
<div class="section bg-pre-blue">
<div class="contain">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Qui excepturi soluta quos nostrum, est ex, maxime repudiandae laborum molestias inventore debitis. Nihil quidem explicabo assumenda debitis quas, beatae molestiae officiis.</p>
</div>
</div>