You've already forked framexEngine-pro
Firts up
This commit is contained in:
139
.gitignore
vendored
139
.gitignore
vendored
@@ -1,138 +1 @@
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# vitepress build output
|
||||
**/.vitepress/dist
|
||||
|
||||
# vitepress cache directory
|
||||
**/.vitepress/cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
/node_modules/
|
||||
19
.vscode/settings.json
vendored
Normal file
19
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeForeground": "#ffffff",
|
||||
"titleBar.inactiveForeground": "#ffffff",
|
||||
"titleBar.activeBackground": "#5920bc",
|
||||
"titleBar.inactiveBackground": "#3f1883"
|
||||
},
|
||||
|
||||
"editor.fontSize": 15,
|
||||
"editor.tabSize": 2,
|
||||
"terminal.integrated.fontSize": 15,
|
||||
"window.zoomLevel": 1,
|
||||
"[Log]": {
|
||||
"editor.fontSize": 14
|
||||
},
|
||||
|
||||
"html-css-class-completion.includeGlobPattern": "public/css/**/*.{css,scss}",
|
||||
"liveServer.settings.port": 5501
|
||||
}
|
||||
74
LICENSE
74
LICENSE
@@ -1,73 +1,5 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
||||
|
||||
Copyright 2026 framex
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Feel free to build anything you want!
|
||||
384
README.md
384
README.md
@@ -1,2 +1,384 @@
|
||||
# framexEngine-pro
|
||||
# Framex Engine
|
||||
|
||||
> A lightweight, high-performance PHP engine for building static sites and modern web applications.
|
||||
|
||||
[](https://php.net)
|
||||
[](https://tailwindcss.com)
|
||||
[](LICENSE)
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [What is Framex?](#what-is-framex)
|
||||
- [Features](#features)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Creating Pages](#creating-pages)
|
||||
- [PHP Pages](#php-pages)
|
||||
- [Markdown Pages](#markdown-pages)
|
||||
- [Templates & Partials](#templates--partials)
|
||||
- [Styling](#styling)
|
||||
- [Helper Functions](#helper-functions)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
---
|
||||
|
||||
## What is Framex?
|
||||
|
||||
**Framex Engine** is a minimal PHP framework designed for developers who want to ship fast static sites without the bloat of traditional CMS platforms or over-engineered frameworks.
|
||||
|
||||
No database. No complex routing files. No plugin ecosystem to maintain. Just **PHP files**, **Markdown content**, and a directory structure that mirrors your URLs.
|
||||
|
||||
Framex is perfect for:
|
||||
|
||||
- Landing pages & marketing sites
|
||||
- Documentation & blogs
|
||||
- Portfolios & personal sites
|
||||
- Prototypes & MVPs
|
||||
- Any site where content changes faster than architecture
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **URL-Driven Routing** | Your folder structure *is* your routing. Zero configuration. |
|
||||
| **Infinite Pages** | Create a file → get a page. No database required. |
|
||||
| **Markdown Support** | Write content in `.md` files. Rendered automatically via Parsedown. |
|
||||
| **Template System** | Switch layouts per page. Reusable partials for DRY templates. |
|
||||
| **Tailwind CSS v4** | Pre-configured with Lightning CSS. Utility-first, blazing fast builds. |
|
||||
| **Asset Pipeline** | Built-in cache busting, file hashing, and development helpers. |
|
||||
| **Zero Dependencies** | Works on any shared host. No Composer required for core features. |
|
||||
| **Dark Theme Ready** | Modern dark UI components included out of the box. |
|
||||
| **Light / Dark Mode** | Automatic system detection with manual toggle. Persisted in localStorage. |
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Requirements
|
||||
|
||||
- PHP 8.0 or higher
|
||||
- Node.js 18+ (for CSS build pipeline)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/yourusername/framex-engine.git my-project
|
||||
cd my-project
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start the development watcher
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Point your web server to the `public/` directory and visit `http://localhost`.
|
||||
|
||||
> **Note:** If you use PHP's built-in server, run it from the project root:
|
||||
> ```bash
|
||||
> php -S localhost:8000 -t public/
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
framexEngine-Base/
|
||||
├── app/
|
||||
│ ├── views/ # Your pages live here
|
||||
│ │ ├── index.php
|
||||
│ │ ├── about.php
|
||||
│ │ └── blog/
|
||||
│ │ └── hello-world.md
|
||||
│ └── about/
|
||||
│
|
||||
├── core/
|
||||
│ ├── classes/
|
||||
│ │ ├── Engine.php # Router & request handler
|
||||
│ │ ├── Framex.php # View renderer
|
||||
│ │ └── Parsedown.php # Markdown parser
|
||||
│ ├── config.php # Constants & configuration
|
||||
│ ├── functions.php # Global helper functions
|
||||
│ └── ignition.php # Application bootstrap
|
||||
│
|
||||
├── templates/
|
||||
│ ├── main.php # Default layout (nav + footer)
|
||||
│ ├── clean.php # Minimal layout
|
||||
│ ├── admin.php # Dashboard layout
|
||||
│ ├── error404.php # 404 error page
|
||||
│ └── partials/
|
||||
│ ├── topmenu.php
|
||||
│ └── footer.php
|
||||
│
|
||||
├── public/
|
||||
│ ├── css/ # Compiled stylesheets
|
||||
│ ├── js/ # Scripts
|
||||
│ ├── images/
|
||||
│ └── index.php # Entry point
|
||||
│
|
||||
├── public/css/src/ # CSS source files
|
||||
│ ├── style.css # Main site styles
|
||||
│ ├── admin.css # Admin panel styles
|
||||
│ └── ...
|
||||
│
|
||||
└── package.json # Build scripts & dependencies
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Creating Pages
|
||||
|
||||
### PHP Pages
|
||||
|
||||
Create a file in `app/views/` and it instantly becomes a URL:
|
||||
|
||||
| File | URL |
|
||||
|------|-----|
|
||||
| `app/views/index.php` | `/` |
|
||||
| `app/views/about.php` | `/about` |
|
||||
| `app/views/blog/post.php` | `/blog/post` |
|
||||
| `app/views/contact/index.php` | `/contact` |
|
||||
|
||||
Example page (`app/views/about.php`):
|
||||
|
||||
```php
|
||||
<?php
|
||||
$data['title'] = "About Us";
|
||||
?>
|
||||
|
||||
<h1>About Framex</h1>
|
||||
<p>This page was created by dropping a single PHP file in the views folder.</p>
|
||||
```
|
||||
|
||||
### Markdown Pages
|
||||
|
||||
Prefer writing in Markdown? Save your content as a `.md` file:
|
||||
|
||||
| File | URL |
|
||||
|------|-----|
|
||||
| `app/views/changelog.md` | `/changelog` |
|
||||
| `app/views/docs/installation.md` | `/docs/installation` |
|
||||
|
||||
Framex automatically detects the extension, runs it through [Parsedown](https://parsedown.org/), and wraps it in your template.
|
||||
|
||||
```markdown
|
||||
# Installation
|
||||
|
||||
1. Clone the repo
|
||||
2. Run `npm install`
|
||||
3. Start hacking
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Templates & Partials
|
||||
|
||||
### Switching Templates
|
||||
|
||||
By default, every page wraps inside `templates/main.php`. You can switch this per page:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$data['title'] = "Admin Dashboard";
|
||||
$data['template'] = "admin"; // Uses templates/admin.php
|
||||
?>
|
||||
```
|
||||
|
||||
Built-in templates:
|
||||
|
||||
| Template | Purpose |
|
||||
|----------|---------|
|
||||
| `main` | Default layout with navigation and footer |
|
||||
| `clean` | Minimal layout, no wrappers |
|
||||
| `admin` | Dashboard sidebar layout |
|
||||
|
||||
### Partials
|
||||
|
||||
Reusable chunks live in `templates/partials/`:
|
||||
|
||||
```php
|
||||
<?php partial('topmenu'); ?>
|
||||
```
|
||||
|
||||
Create your own:
|
||||
|
||||
```bash
|
||||
touch templates/partials/newsletter.php
|
||||
```
|
||||
|
||||
```php
|
||||
<!-- templates/partials/newsletter.php -->
|
||||
<form action="/subscribe" method="post">
|
||||
<input type="email" name="email" placeholder="Enter your email">
|
||||
<button type="submit">Subscribe</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
Include it anywhere:
|
||||
|
||||
```php
|
||||
<?php partial('newsletter'); ?>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Styling
|
||||
|
||||
Framex ships with **Tailwind CSS v4** pre-configured. The build pipeline uses the Tailwind CLI with Lightning CSS for maximum performance.
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Watch for changes and rebuild automatically
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
# Build minified CSS for production
|
||||
npm run build
|
||||
```
|
||||
|
||||
### CSS Source Files
|
||||
|
||||
| File | Output | Purpose |
|
||||
|------|--------|---------|
|
||||
| `public/css/src/style.css` | `public/css/style.css` | Main site styles |
|
||||
| `public/css/src/admin.css` | `public/css/admin.css` | Admin panel styles |
|
||||
| `public/css/src/responsive.css` | `public/css/responsive.css` | Responsive overrides |
|
||||
| `public/css/src/vendors.css` | `public/css/vendors.css` | Third-party styles |
|
||||
|
||||
### Using Custom CSS
|
||||
|
||||
You are not locked into Tailwind. The CSS pipeline is completely optional. You can:
|
||||
|
||||
- Write vanilla CSS in `public/css/src/`
|
||||
- Use Sass/SCSS (still installed)
|
||||
- Link external CSS frameworks from a CDN
|
||||
- Replace the entire build pipeline
|
||||
|
||||
---
|
||||
|
||||
## Light & Dark Mode
|
||||
|
||||
Framex includes a built-in light/dark mode toggle that respects the user's system preference and persists their choice in `localStorage`.
|
||||
|
||||
- **Automatic detection** — Checks `prefers-color-scheme: dark` on first visit
|
||||
- **Manual toggle** — Sun/moon button in the navbar
|
||||
- **Instant switch** — Zero page reload, powered by Tailwind's `dark:` utilities
|
||||
- **Persistent** — Choice is saved and restored on every page
|
||||
|
||||
To style for dark mode in your templates, use Tailwind's `dark:` prefix:
|
||||
|
||||
```html
|
||||
<div class="bg-white text-slate-900 dark:bg-slate-950 dark:text-slate-100">
|
||||
<!-- Content that adapts to the current theme -->
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SEO & Meta Tags
|
||||
|
||||
Every page can define custom meta descriptions and social sharing images through the `$data` array. Framex automatically generates the following tags in the `<head>`:
|
||||
|
||||
- `<meta name="description">` — Search engine snippet
|
||||
- `<meta property="og:*">` — Open Graph (Facebook, LinkedIn, Discord)
|
||||
- `<meta name="twitter:*">` — Twitter/X Cards
|
||||
|
||||
### Setting meta per page
|
||||
|
||||
```php
|
||||
<?php
|
||||
$data['title'] = "My Awesome Page";
|
||||
$data['metaDescription'] = "A concise description for search engines and social shares.";
|
||||
$data['metaImage'] = "/images/og-my-page.png"; // Optional
|
||||
?>
|
||||
```
|
||||
|
||||
If you do not set a custom description or image, sensible defaults are used automatically.
|
||||
|
||||
---
|
||||
|
||||
## Helper Functions
|
||||
|
||||
Framex includes a set of global helpers to speed up development:
|
||||
|
||||
| Function | Usage | Description |
|
||||
|----------|-------|-------------|
|
||||
| `asset()` | `asset('css/style.css')` | Versioned asset URLs with cache busting |
|
||||
| `image()` | `image(800, 600)` | Random placeholder images via Picsum |
|
||||
| `partial()` | `partial('footer')` | Include reusable template chunks |
|
||||
| `e()` | `e($userInput)` | Escape HTML entities (XSS protection) |
|
||||
| `pre()` | `pre($array, true)` | Debug print_r with optional die |
|
||||
| `summarize()` | `summarize($text, 100)` | Truncate text with ellipsis |
|
||||
| `urlSlug()` | `urlSlug($title)` | Convert strings to URL-friendly slugs |
|
||||
|
||||
### Asset Helper Options
|
||||
|
||||
```php
|
||||
// Basic usage
|
||||
<link rel="stylesheet" href="<?= asset('css/style.css'); ?>">
|
||||
|
||||
// With file hash for cache busting
|
||||
<script src="<?= asset('js/app.js', ['hash' => true]); ?>"></script>
|
||||
|
||||
// With custom prefix
|
||||
<link rel="stylesheet" href="<?= asset('css/style.css', ['prefix' => 'v']); ?>">
|
||||
|
||||
// Absolute URL
|
||||
<meta property="og:image" content="<?= asset('images/og.png', ['absolute' => true]); ?>">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `core/config.php` to customize your setup:
|
||||
|
||||
```php
|
||||
define('DEV_MODE', true); // Toggle development mode
|
||||
define('SITE_URL', 'https://...'); // Your site URL
|
||||
define('CSS', '/css/'); // CSS directory
|
||||
define('JS', '/js/'); // JS directory
|
||||
```
|
||||
|
||||
**Development Mode** (`DEV_MODE = true`):
|
||||
- CSS URLs append a unique query string on every refresh
|
||||
- Errors are more verbose
|
||||
|
||||
**Production Mode** (`DEV_MODE = false`):
|
||||
- Assets use file-hash based cache busting
|
||||
- Cleaner error handling
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Feel free to open issues or submit pull requests.
|
||||
|
||||
1. Fork the repository
|
||||
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
Framex Engine is open-source software licensed under the [MIT License](LICENSE).
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
Built with ❤️ by <strong>Prodigital Framex</strong>
|
||||
</p>
|
||||
|
||||
68
app/views/about/index.md
Normal file
68
app/views/about/index.md
Normal 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.
|
||||
|
||||

|
||||
|
||||
## 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.**
|
||||
53
app/views/blocks/accordion/accordion-1.php
Normal file
53
app/views/blocks/accordion/accordion-1.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<div class="section">
|
||||
<div class="band">
|
||||
<div class="card space-y-4">
|
||||
<details class="group [&_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 [&_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 [&_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>
|
||||
77
app/views/blocks/accordion/index.php
Normal file
77
app/views/blocks/accordion/index.php
Normal 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>
|
||||
40
app/views/blocks/blog-list/blog-list-1.php
Normal file
40
app/views/blocks/blog-list/blog-list-1.php
Normal 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>
|
||||
67
app/views/blocks/blog-list/blog-list-2.php
Normal file
67
app/views/blocks/blog-list/blog-list-2.php
Normal 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>
|
||||
96
app/views/blocks/blog-list/blog-list-3.php
Normal file
96
app/views/blocks/blog-list/blog-list-3.php
Normal 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>
|
||||
155
app/views/blocks/blog-list/blog-list-4.php
Normal file
155
app/views/blocks/blog-list/blog-list-4.php
Normal 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>
|
||||
77
app/views/blocks/blog-list/index.php
Normal file
77
app/views/blocks/blog-list/index.php
Normal 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>
|
||||
26
app/views/blocks/heros/hero-1.php
Normal file
26
app/views/blocks/heros/hero-1.php
Normal 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>
|
||||
33
app/views/blocks/heros/hero-2.php
Normal file
33
app/views/blocks/heros/hero-2.php
Normal 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>
|
||||
77
app/views/blocks/heros/index.php
Normal file
77
app/views/blocks/heros/index.php
Normal 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>
|
||||
77
app/views/blocks/index.php
Normal file
77
app/views/blocks/index.php
Normal 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>
|
||||
77
app/views/blocks/sections/index.php
Normal file
77
app/views/blocks/sections/index.php
Normal 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>
|
||||
20
app/views/blocks/sections/section-1.php
Normal file
20
app/views/blocks/sections/section-1.php
Normal 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>
|
||||
88
app/views/demo/blog-list.php
Normal file
88
app/views/demo/blog-list.php
Normal 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>
|
||||
98
app/views/demo/blog-post.php
Normal file
98
app/views/demo/blog-post.php
Normal 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
86
app/views/demo/index.php
Normal 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
54
app/views/demo/page.md
Normal 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
415
app/views/docs/index.md
Normal 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
178
app/views/index.php
Normal 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"><?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">?></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
11
app/views/lab.php
Normal 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>
|
||||
1
core/.htaccess
Normal file
1
core/.htaccess
Normal file
@@ -0,0 +1 @@
|
||||
Deny from all
|
||||
44
core/classes/Engine.php
Normal file
44
core/classes/Engine.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class Engine
|
||||
{
|
||||
protected $appPath;
|
||||
protected $templatesPath;
|
||||
public $viewPath;
|
||||
|
||||
public function __construct($appPath = APP, $templatesPath = TEMPLATES)
|
||||
{
|
||||
$this->appPath = rtrim($appPath, '/') . '/';
|
||||
$this->templatesPath = rtrim($templatesPath, '/') . '/';
|
||||
|
||||
$this->viewPath = $this->determineViewPath($this->_urlParams());
|
||||
}
|
||||
|
||||
protected function determineViewPath($urlParams): string
|
||||
{
|
||||
$path1 = $this->appPath . "app/views" . $urlParams . '.php';
|
||||
$path2 = $this->appPath . "app/views" . $urlParams . '/index.php';
|
||||
$path3 = $this->appPath . "app/views" . $urlParams . '.md';
|
||||
$path4 = $this->appPath . "app/views" . $urlParams . '/index.md';
|
||||
$path404 = $this->templatesPath . 'error404.php';
|
||||
|
||||
if (file_exists($path1)) {
|
||||
return $path1;
|
||||
} elseif (file_exists($path2)) {
|
||||
return $path2;
|
||||
} elseif (file_exists($path3)) {
|
||||
return $path3;
|
||||
} elseif (file_exists($path4)) {
|
||||
return $path4;
|
||||
} else {
|
||||
return $path404;
|
||||
}
|
||||
}
|
||||
|
||||
private function _urlParams(): string
|
||||
{
|
||||
$serverURL = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
|
||||
// Normalize URL by removing trailing slashes
|
||||
return rtrim($serverURL, "/");
|
||||
}
|
||||
}
|
||||
49
core/classes/Framex.php
Normal file
49
core/classes/Framex.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
class Framex extends Engine
|
||||
{
|
||||
private $viewsPath;
|
||||
|
||||
public function __construct($appPath = APP, $templatesPath = TEMPLATES, $viewsPath = VIEWS)
|
||||
{
|
||||
parent::__construct($appPath, $templatesPath);
|
||||
$this->viewsPath = rtrim($viewsPath, '/') . '/';
|
||||
}
|
||||
|
||||
public function renderTemplate(string $file, array $data = []): void
|
||||
{
|
||||
$filePath = $this->appPath . "templates/" . $file . ".php";
|
||||
if (file_exists($filePath)) {
|
||||
if (!empty($data)) {
|
||||
extract($data);
|
||||
}
|
||||
require_once $filePath;
|
||||
} else {
|
||||
// Handle the error appropriately, e.g., log it or throw an exception
|
||||
echo "Template file not found: " . htmlspecialchars($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$file = $this->viewPath;
|
||||
$data = [];
|
||||
|
||||
if (!file_exists($file)) {
|
||||
$file = $this->viewsPath . 'templates/404.php';
|
||||
}
|
||||
|
||||
if (strtolower(pathinfo($file, PATHINFO_EXTENSION)) === 'md') {
|
||||
$markdown = file_get_contents($file);
|
||||
$parsedown = new Parsedown();
|
||||
$data['view'] = '<article class="prose prose-shell">' . $parsedown->text($markdown) . '</article>';
|
||||
} else {
|
||||
ob_start();
|
||||
require_once $file;
|
||||
$data['view'] = ob_get_clean();
|
||||
}
|
||||
|
||||
$template = $data['template'] ?? 'main';
|
||||
$this->renderTemplate($template, $data);
|
||||
}
|
||||
}
|
||||
1995
core/classes/Parsedown.php
Normal file
1995
core/classes/Parsedown.php
Normal file
File diff suppressed because it is too large
Load Diff
33
core/config.php
Normal file
33
core/config.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
define('DEV_MODE', true);
|
||||
define('TOPLINK', 'javascript:void(0)');
|
||||
|
||||
define('SITE_URL', $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'] . '/');
|
||||
define('SITE_URI', (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
|
||||
|
||||
define('APP', str_replace("\\", "/", dirname(__DIR__) . '/'));
|
||||
|
||||
define('ROOT', $_SERVER['DOCUMENT_ROOT']);
|
||||
define('CORE', APP . 'core/');
|
||||
define('VIEWS', APP . 'app/views/');
|
||||
define('TEMPLATES', APP . 'templates/');
|
||||
|
||||
define('CSS', '/css/');
|
||||
define('JS', '/js/');
|
||||
define('IMAGES', '/images/');
|
||||
define('IMG', '/images/img/');
|
||||
|
||||
define('HOST', 'localhost');
|
||||
define('USER', 'sk');
|
||||
define('PASSWORD', 'Ser026322!!');
|
||||
define('DATABASE', 'framex-alpha');
|
||||
|
||||
date_default_timezone_set('Europe/Athens');
|
||||
|
||||
function site(string $param): string {
|
||||
$values = [
|
||||
'core' => 'ELLHNIKA',
|
||||
];
|
||||
return $values[$param] ?? '';
|
||||
}
|
||||
|
||||
152
core/functions.php
Normal file
152
core/functions.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Enhanced version with additional features
|
||||
* usage:
|
||||
* echo asset('css/style.css');
|
||||
* asset('js/plugins.js', ['prefix' => 'framex', 'hash' => true])
|
||||
* echo asset('js/app.js', ['absolute' => true, 'hash' => true]);
|
||||
* echo asset('images/logo.png', ['fallback' => '/images/default-logo.png']);
|
||||
*/
|
||||
function asset($path, $options = []) {
|
||||
// Default options
|
||||
$defaults = [
|
||||
'absolute' => false,
|
||||
'fallback' => null, // Fallback URL if file doesn't exist
|
||||
'prefix' => 'v', // Version parameter prefix
|
||||
'hash' => false // Use file hash instead of timestamp
|
||||
];
|
||||
|
||||
$options = array_merge($defaults, $options);
|
||||
|
||||
// Remove leading slash
|
||||
$path = ltrim($path, '/');
|
||||
|
||||
$documentRoot = $_SERVER['DOCUMENT_ROOT'];
|
||||
$fullPath = $documentRoot . '/' . $path;
|
||||
|
||||
// Generate version parameter
|
||||
$version = '';
|
||||
if (file_exists($fullPath)) {
|
||||
if ($options['hash']) {
|
||||
// Use file hash (more reliable for cache busting)
|
||||
$version = '?' . $options['prefix'] . '=' . substr(md5_file($fullPath), 0, 8);
|
||||
} else {
|
||||
// Use modification time
|
||||
$version = '?' . $options['prefix'] . '=' . filemtime($fullPath);
|
||||
}
|
||||
} elseif ($options['fallback']) {
|
||||
// Return fallback URL if provided
|
||||
return $options['fallback'];
|
||||
} else {
|
||||
error_log("Asset not found: " . $fullPath);
|
||||
}
|
||||
|
||||
// Build URL
|
||||
if ($options['absolute']) {
|
||||
$protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
|
||||
$host = $_SERVER['HTTP_HOST'];
|
||||
return $protocol . '://' . $host . '/' . $path . $version;
|
||||
} else {
|
||||
return '/' . $path . $version;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get Partials
|
||||
*
|
||||
* @param string $name The name of the partial to include.
|
||||
* @throws Exception If the partial does not exist.
|
||||
*/
|
||||
function partial(string $name): void {
|
||||
$path = TEMPLATES . '/partials/' . $name . '.php';
|
||||
if (file_exists($path)) {
|
||||
include $path;
|
||||
} else {
|
||||
throw new Exception("Partial '{$name}' doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function codeBlock($code, $language = 'php') {
|
||||
// 1. Convert special characters to HTML entities to prevent execution/bugs
|
||||
$safe_code = htmlspecialchars($code, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// 2. Wrap in <pre> and <code> tags with the appropriate class
|
||||
$output = '<pre class="line-numbers rounded"><code class=" code-font language-' . $language . '">';
|
||||
$output .= $safe_code;
|
||||
$output .= '</code></pre>';
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pre Helper
|
||||
*
|
||||
* @param mixed $array The data to print.
|
||||
* @param bool|null $die Whether to stop execution after printing.
|
||||
* @param bool $report Whether to echo the data directly.
|
||||
*/
|
||||
function pre($array, ?bool $die = null, bool $report = false): void {
|
||||
if ($report) {
|
||||
echo $array;
|
||||
}
|
||||
echo '<pre>' . print_r($array, true) . '</pre>';
|
||||
if ($die) {
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Random Image
|
||||
*
|
||||
* @param int $width The width of the image.
|
||||
* @param int $height The height of the image.
|
||||
* @return string The URL of the random image.
|
||||
*/
|
||||
function image(int $width = 960, int $height = 576): string {
|
||||
return 'https://picsum.photos/' . $width . '/' . $height . '?v=' . rand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize Input
|
||||
*
|
||||
* @param string $data The data to sanitize.
|
||||
* @return string The sanitized data.
|
||||
*/
|
||||
function e(string $data): string {
|
||||
return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize Text
|
||||
*
|
||||
* @param string $text The text to summarize.
|
||||
* @param int $maxLength The maximum length of the summary.
|
||||
* @return string The summarized text.
|
||||
*/
|
||||
function summarize(string $text, int $maxLength = 100): string {
|
||||
if (strlen($text) <= $maxLength) {
|
||||
return $text;
|
||||
}
|
||||
$summary = substr($text, 0, $maxLength);
|
||||
return $summary . '...';
|
||||
}
|
||||
|
||||
function urlSlug($value, $transliteration = true)
|
||||
{
|
||||
if (extension_loaded('intl') && $transliteration == true) {
|
||||
$transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
|
||||
$value = $transliterator->transliterate($value);
|
||||
}
|
||||
$slug = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
|
||||
$slug = preg_replace('~[^\pL\d]+~u', '-', $slug);
|
||||
$slug = trim($slug, '-');
|
||||
$slug = strtolower($slug);
|
||||
return $slug;
|
||||
}
|
||||
|
||||
17
core/ignition.php
Normal file
17
core/ignition.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
ini_set('session.cookie_httponly', 1);
|
||||
ini_set('session.cookie_secure', 1); // Only if using HTTPS
|
||||
|
||||
session_start();
|
||||
|
||||
require_once 'config.php';
|
||||
require_once 'functions.php';
|
||||
|
||||
spl_autoload_register(function ($className) {
|
||||
$file = APP . 'core/classes/' . $className . '.php';
|
||||
if (file_exists($file)) {
|
||||
include $file;
|
||||
}
|
||||
});
|
||||
|
||||
1097
package-lock.json
generated
Normal file
1097
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
package.json
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "prodigital-framex",
|
||||
"version": "1.0.0",
|
||||
"description": "Framex Project",
|
||||
"keywords": [
|
||||
"Framex"
|
||||
],
|
||||
"author": "Prodigital Framex",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"build:css": "tailwindcss -i ./public/css/src/style.css -o ./public/css/style.css --minify",
|
||||
"watch:css": "tailwindcss -i ./public/css/src/style.css -o ./public/css/style.css --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/cli": "^4.3.0",
|
||||
"tailwindcss": "^4.3.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
}
|
||||
8
public/.htaccess
Normal file
8
public/.htaccess
Normal file
@@ -0,0 +1,8 @@
|
||||
Options +FollowSymLinks
|
||||
RewriteEngine On
|
||||
|
||||
# Ignore existing directories (!-d) and files (!-f)
|
||||
RewriteCond %{SCRIPT_FILENAME} !-d
|
||||
RewriteCond %{SCRIPT_FILENAME} !-f
|
||||
|
||||
RewriteRule ^.*$ ./index.php
|
||||
2
public/css/responsive.css
Normal file
2
public/css/responsive.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/* Tailwind responsive utilities are included automatically via the main styles.
|
||||
Add any extra responsive overrides here if needed. */
|
||||
2
public/css/src/responsive.css
Normal file
2
public/css/src/responsive.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/* Tailwind responsive utilities are included automatically via the main styles.
|
||||
Add any extra responsive overrides here if needed. */
|
||||
449
public/css/src/style.css
Normal file
449
public/css/src/style.css
Normal file
@@ -0,0 +1,449 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,100..900&display=swap");
|
||||
@import "tailwindcss";
|
||||
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
@source "../../../templates/**/*.php";
|
||||
@source "../../../app/**/*.php";
|
||||
@source "../../../app/**/*.md";
|
||||
|
||||
@theme {
|
||||
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply flex min-h-screen flex-col bg-slate-50 text-slate-950 antialiased transition-colors duration-300 dark:bg-slate-950 dark:text-slate-100;
|
||||
font-feature-settings:
|
||||
"cv02",
|
||||
"cv03",
|
||||
"cv04",
|
||||
"cv11";
|
||||
}
|
||||
|
||||
::selection {
|
||||
@apply bg-blue-600 text-white;
|
||||
}
|
||||
|
||||
a {
|
||||
@apply transition-colors;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
@apply outline-2 outline-offset-2 outline-blue-500;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
@apply grow shrink-0 basis-auto;
|
||||
}
|
||||
|
||||
.contain, .band {
|
||||
@apply mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
.contain-narrow, .band-narrow {
|
||||
@apply mx-auto w-full max-w-3xl px-4 sm:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
.contain-wide, .band-wide {
|
||||
@apply mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
footer {
|
||||
@apply grow-0;
|
||||
}
|
||||
|
||||
.wrp {
|
||||
@apply mx-auto w-full max-w-6xl px-4 sm:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
.section, section {
|
||||
@apply py-10 md:py-16;
|
||||
}
|
||||
|
||||
.section-tight {
|
||||
@apply py-10 md:py-14;
|
||||
}
|
||||
|
||||
.text-gradient {@apply bg-linear-to-r from-indigo-400 to-pink-600 bg-clip-text text-transparent}
|
||||
|
||||
.eyebrow {
|
||||
@apply font-semibold tracking-widest text-blue-600 dark:text-blue-400;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply inline-flex min-h-11 items-center justify-center gap-2 rounded-lg px-5 py-2.5 text-sm font-semibold leading-none transition duration-200 focus-visible:outline-2 focus-visible:outline-offset-2 disabled:pointer-events-none disabled:opacity-50;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply bg-blue-600 text-white shadow-sm shadow-blue-600/25 hover:bg-blue-500 focus-visible:outline-blue-500 dark:bg-blue-500 dark:hover:bg-blue-400;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply border border-slate-200 bg-white text-slate-900 hover:border-slate-300 hover:bg-slate-100 focus-visible:outline-blue-500 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-100 dark:hover:border-slate-700 dark:hover:bg-slate-800;
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
@apply text-slate-700 hover:bg-slate-100 hover:text-slate-950 focus-visible:outline-blue-500 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply rounded-lg border border-slate-200 bg-white p-6 shadow-sm shadow-slate-950/5 transition-colors dark:border-slate-800 dark:bg-slate-900/80 dark:shadow-black/20;
|
||||
}
|
||||
|
||||
.bg-pre-blue {
|
||||
@apply bg-blue-50 text-blue-950 dark:bg-blue-950/35 dark:text-blue-50;
|
||||
}
|
||||
|
||||
.bg-pre-emerald {
|
||||
@apply bg-emerald-50 text-emerald-950 dark:bg-emerald-950/35 dark:text-emerald-50;
|
||||
}
|
||||
|
||||
.bg-pre-amber {
|
||||
@apply bg-amber-50 text-amber-950 dark:bg-amber-950/35 dark:text-amber-50;
|
||||
}
|
||||
|
||||
.bg-pre-rose {
|
||||
@apply bg-rose-50 text-rose-950 dark:bg-rose-950/35 dark:text-rose-50;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
@apply rounded-md px-3 py-2 text-sm font-medium text-slate-700 hover:bg-slate-100 hover:text-slate-950 dark:text-slate-300 dark:hover:bg-slate-900 dark:hover:text-white;
|
||||
}
|
||||
|
||||
.prose-shell {
|
||||
@apply mx-auto max-w-4xl px-4 py-16 sm:px-6 lg:px-8;
|
||||
}
|
||||
|
||||
.prose {
|
||||
color: #475569;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.dark .prose {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.prose > * + * {
|
||||
margin-top: 1.25em;
|
||||
}
|
||||
|
||||
.prose h1,
|
||||
.prose h2,
|
||||
.prose h3,
|
||||
.prose h4,
|
||||
.prose h5,
|
||||
.prose h6 {
|
||||
color: #0f172a;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.dark .prose h1,
|
||||
.dark .prose h2,
|
||||
.dark .prose h3,
|
||||
.dark .prose h4,
|
||||
.dark .prose h5,
|
||||
.dark .prose h6 {
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.prose h1 {
|
||||
font-size: 2.25rem;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.prose h2 {
|
||||
font-size: 1.75rem;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 0.75em;
|
||||
padding-bottom: 0.4em;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.dark .prose h2 {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.prose h3 {
|
||||
font-size: 1.375rem;
|
||||
margin-top: 1.75em;
|
||||
margin-bottom: 0.6em;
|
||||
}
|
||||
|
||||
.prose h4 {
|
||||
font-size: 1.125rem;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.prose h5,
|
||||
.prose h6 {
|
||||
font-size: 1rem;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.dark .prose h5,
|
||||
.dark .prose h6 {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.prose p {
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
.prose a {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
|
||||
.dark .prose a {
|
||||
color: #60a5fa;
|
||||
}
|
||||
|
||||
.prose a:hover {
|
||||
color: #1d4ed8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dark .prose a:hover {
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
.prose strong,
|
||||
.prose b {
|
||||
color: #0f172a;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dark .prose strong,
|
||||
.dark .prose b {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.prose em,
|
||||
.prose i {
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.dark .prose em,
|
||||
.dark .prose i {
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.prose ul,
|
||||
.prose ol {
|
||||
margin-top: 1.25em;
|
||||
margin-bottom: 1.25em;
|
||||
padding-left: 1.625em;
|
||||
}
|
||||
|
||||
.prose ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
.prose ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.prose ul > li,
|
||||
.prose ol > li {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 0.375em;
|
||||
}
|
||||
|
||||
.prose ul > li::marker,
|
||||
.prose ol > li::marker {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.dark .prose ul > li::marker,
|
||||
.dark .prose ol > li::marker {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.prose ul ul,
|
||||
.prose ul ol,
|
||||
.prose ol ul,
|
||||
.prose ol ol {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.prose pre {
|
||||
background-color: #f1f5f9;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.25em;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
overflow-x: auto;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.dark .prose pre {
|
||||
background-color: #141f38;
|
||||
border-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.prose pre code {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
color: #334155;
|
||||
font-family:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
}
|
||||
|
||||
.dark .prose pre code {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.06);
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.2em 0.4em;
|
||||
font-size: 0.875em;
|
||||
color: #1d4ed8;
|
||||
font-family:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
}
|
||||
|
||||
.dark .prose code {
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(255, 255, 255, 0.06);
|
||||
color: #93c5fd;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
padding-left: 1.25em;
|
||||
border-left: 3px solid #2563eb;
|
||||
font-style: italic;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.dark .prose blockquote {
|
||||
border-left-color: #3b82f6;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
.prose blockquote p:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.prose blockquote p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.prose table {
|
||||
width: 100%;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.prose thead {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.dark .prose thead {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.prose th {
|
||||
padding: 0.75em 1em;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.dark .prose th {
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.prose td {
|
||||
padding: 0.625em 1em;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.dark .prose td {
|
||||
border-bottom-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.prose tbody tr:nth-child(even) {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.dark .prose tbody tr:nth-child(even) {
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.prose tbody tr:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
.dark .prose tbody tr:hover {
|
||||
background-color: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.prose img {
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.prose hr {
|
||||
margin-top: 2.5em;
|
||||
margin-bottom: 2.5em;
|
||||
border: 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.dark .prose hr {
|
||||
border-top-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.prose li > p,
|
||||
.prose blockquote > p {
|
||||
margin-top: 0.75em;
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
||||
}
|
||||
2
public/css/src/vendors.css
Normal file
2
public/css/src/vendors.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/* Vendor styles — add third-party CSS here or @import them.
|
||||
Tailwind is NOT imported here by default. */
|
||||
2787
public/css/style.css
Normal file
2787
public/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
2
public/css/vendors.css
Normal file
2
public/css/vendors.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/* Vendor styles — add third-party CSS here or @import them.
|
||||
Tailwind is NOT imported here by default. */
|
||||
BIN
public/images/thanos.png
Normal file
BIN
public/images/thanos.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
7
public/index.php
Normal file
7
public/index.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
require "../core/ignition.php";
|
||||
|
||||
$framex = new Framex;
|
||||
|
||||
$framex->init();
|
||||
58
public/js/init.js
Normal file
58
public/js/init.js
Normal file
@@ -0,0 +1,58 @@
|
||||
"use strict";
|
||||
|
||||
(function () {
|
||||
const framexEngine = {
|
||||
init() {
|
||||
this.bindThemeToggle();
|
||||
this.bindMobileMenu();
|
||||
},
|
||||
|
||||
bindThemeToggle() {
|
||||
const buttons = document.querySelectorAll("[data-theme-toggle]");
|
||||
if (!buttons.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const applyTheme = (theme) => {
|
||||
const isDark = theme === "dark";
|
||||
document.documentElement.classList.toggle("dark", isDark);
|
||||
try {
|
||||
localStorage.setItem("framex-theme", theme);
|
||||
} catch (error) {}
|
||||
buttons.forEach((button) => {
|
||||
button.setAttribute("aria-pressed", String(isDark));
|
||||
});
|
||||
};
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const nextTheme = document.documentElement.classList.contains("dark")
|
||||
? "light"
|
||||
: "dark";
|
||||
applyTheme(nextTheme);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
bindMobileMenu() {
|
||||
const button = document.querySelector("[data-mobile-menu-toggle]");
|
||||
const menu = document.querySelector("[data-mobile-menu]");
|
||||
if (!button || !menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
button.addEventListener("click", () => {
|
||||
const isOpen = button.getAttribute("aria-expanded") === "true";
|
||||
button.setAttribute("aria-expanded", String(!isOpen));
|
||||
menu.classList.toggle("hidden", isOpen);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", () => framexEngine.init());
|
||||
} else {
|
||||
framexEngine.init();
|
||||
}
|
||||
})();
|
||||
31
templates/clean.php
Normal file
31
templates/clean.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= (isset($title) ? $title : 'Framex Engine') ?></title>
|
||||
<link rel="stylesheet" href="<?= asset('css/style.css') ?>">
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const stored = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
if (stored === 'dark' || (!stored && prefersDark)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100">
|
||||
|
||||
<main class="wrp">
|
||||
<?= $view ?>
|
||||
</main>
|
||||
|
||||
<script src="<?= asset('js/init.js') ?>"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
57
templates/error404.php
Normal file
57
templates/error404.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
$data['title'] = "Page Not Found — Framex Engine";
|
||||
$data['metaDescription'] = "The page you are looking for does not exist. Return to Framex Engine home.";
|
||||
$data['bodyClass'] = "bg-gray-50 text-slate-900 dark:bg-slate-950 dark:text-slate-100 antialiased";
|
||||
?>
|
||||
|
||||
<section class="relative isolate flex min-h-[80vh] items-center justify-center overflow-hidden">
|
||||
<!-- Background glow -->
|
||||
<div class="absolute inset-0 -z-10">
|
||||
<div class="absolute inset-0 bg-gray-50 dark:bg-slate-950"></div>
|
||||
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-indigo-600/10 rounded-full blur-[120px]"></div>
|
||||
<div class="absolute top-1/3 right-1/4 w-[300px] h-[300px] bg-violet-600/10 rounded-full blur-[80px]"></div>
|
||||
</div>
|
||||
|
||||
<div class="mx-auto max-w-2xl px-4 text-center">
|
||||
<!-- 404 Number -->
|
||||
<div class="relative inline-block">
|
||||
<h1 class="text-9xl font-black tracking-tighter text-black/5 dark:text-white/5 select-none sm:text-[12rem]">
|
||||
404
|
||||
</h1>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<span class="text-7xl font-black tracking-tight bg-gradient-to-b from-white to-black/20 dark:to-white/20 bg-clip-text text-transparent sm:text-9xl">
|
||||
404
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message -->
|
||||
<p class="mt-8 text-2xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-3xl">
|
||||
Page not found
|
||||
</p>
|
||||
<p class="mt-4 text-lg text-slate-600 dark:text-slate-400 max-w-md mx-auto">
|
||||
The page you are looking for does not exist. It might have been moved, renamed, or maybe it never existed in the first place.
|
||||
</p>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="mt-10 flex flex-col sm:flex-row items-center justify-center gap-4">
|
||||
<a href="/" class="inline-flex items-center justify-center rounded-xl bg-white px-8 py-3.5 text-base font-semibold text-slate-950 shadow-lg shadow-black/10 dark:shadow-white/10 hover:bg-slate-200 transition-all">
|
||||
<svg class="mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18" />
|
||||
</svg>
|
||||
Back to Home
|
||||
</a>
|
||||
<a href="/pages/features" class="inline-flex items-center justify-center rounded-xl border border-black/10 dark:border-white/10 bg-black/5 dark:bg-white/5 px-8 py-3.5 text-base font-semibold text-slate-900 dark:text-white hover:bg-black/10 dark:hover:bg-white/10 transition-all">
|
||||
Explore Features
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- File tree hint -->
|
||||
<div class="mt-12 inline-block rounded-xl border border-black/5 dark:border-white/5 bg-black/[0.02] dark:bg-white/[0.02] px-6 py-4 text-left">
|
||||
<p class="text-xs font-mono text-slate-500 dark:text-slate-500 mb-2">Did you mean to create this file?</p>
|
||||
<code class="block font-mono text-sm text-indigo-400">
|
||||
app/views<?= htmlspecialchars($_SERVER['REQUEST_URI']) ?>.php
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
57
templates/main.php
Normal file
57
templates/main.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
$metaTitle = isset($title) ? $title : 'Framex Engine';
|
||||
$metaDescription = isset($metaDescription) ? $metaDescription : 'Framex Engine — A lightweight, high-performance PHP engine for building static sites and modern web applications.';
|
||||
$metaImage = isset($metaImage) ? $metaImage : image(1200, 630);
|
||||
$metaUrl = SITE_URI;
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="scroll-smooth">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= $metaTitle ?></title>
|
||||
<meta name="description" content="<?= e($metaDescription) ?>">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="<?= $metaUrl ?>">
|
||||
<meta property="og:title" content="<?= e($metaTitle) ?>">
|
||||
<meta property="og:description" content="<?= e($metaDescription) ?>">
|
||||
<meta property="og:image" content="<?= $metaImage ?>">
|
||||
|
||||
<!-- Twitter Card -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:url" content="<?= $metaUrl ?>">
|
||||
<meta name="twitter:title" content="<?= e($metaTitle) ?>">
|
||||
<meta name="twitter:description" content="<?= e($metaDescription) ?>">
|
||||
<meta name="twitter:image" content="<?= $metaImage ?>">
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var theme = null;
|
||||
try {
|
||||
theme = localStorage.getItem('framex-theme');
|
||||
} catch (error) {}
|
||||
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
if (theme === 'dark' || (!theme && prefersDark)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="<?= asset('css/style.css') ?>">
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="<?php echo (isset($bodyClass) && $bodyClass !== '' ? $bodyClass : 'bg-slate-50 text-slate-950 dark:bg-slate-950 dark:text-slate-100') ?>">
|
||||
<div class="wrap">
|
||||
<?= partial('topmenu') ?>
|
||||
<?= $view ?>
|
||||
</div>
|
||||
<?= partial('footer') ?>
|
||||
<script src="<?= asset('js/init.js', ['prefix' => 'framex', 'hash' => true]) ?>"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
39
templates/partials/footer.php
Normal file
39
templates/partials/footer.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php ?>
|
||||
<footer class="border-t border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-950">
|
||||
<div class="contain grid gap-10 py-10 md:grid-cols-[1.4fr_1fr_1fr]">
|
||||
<div>
|
||||
<a href="/" class="inline-flex items-center gap-3 font-semibold text-slate-950 dark:text-white" aria-label="Framex home">
|
||||
<span class="grid size-9 place-items-center rounded-lg bg-blue-600 text-sm font-bold text-white uppercase">Fx</span>
|
||||
<span>Framex v1.0.0</span>
|
||||
</a>
|
||||
<p class="mt-4 max-w-md text-sm leading-6 text-slate-600 dark:text-slate-400">
|
||||
A lightweight PHP engine for fast static sites, markdown pages, and modern frontends powered by Tailwind CSS.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-slate-950 dark:text-white">Build</h2>
|
||||
<div class="mt-4 grid gap-3 text-sm text-slate-600 dark:text-slate-400">
|
||||
<a class="hover:text-blue-600 dark:hover:text-blue-400" href="/#features">Features</a>
|
||||
<a class="hover:text-blue-600 dark:hover:text-blue-400" href="/#markdown">Markdown</a>
|
||||
<a class="hover:text-blue-600 dark:hover:text-blue-400" href="/#themes">Theme system</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2 class="text-sm font-semibold text-slate-950 dark:text-white">Project</h2>
|
||||
<div class="mt-4 grid gap-3 text-sm text-slate-600 dark:text-slate-400">
|
||||
<a class="hover:text-blue-600 dark:hover:text-blue-400" href="/docs">Docs</a>
|
||||
<a class="hover:text-blue-600 dark:hover:text-blue-400" href="/#start">Get started</a>
|
||||
<a class="hover:text-blue-600 dark:hover:text-blue-400" href="/">Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-200 py-5 dark:border-slate-800">
|
||||
<div class="contain flex flex-col gap-2 text-sm text-slate-500 sm:flex-row sm:items-center sm:justify-between dark:text-slate-400">
|
||||
<p>© <?= date('Y') ?> Framex Engine. Built for practical PHP sites.</p>
|
||||
<p>Tailwind CSS 4 ready.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
57
templates/partials/topmenu.php
Normal file
57
templates/partials/topmenu.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php ?>
|
||||
<header class="sticky top-0 z-50 border-b border-slate-200/80 bg-white/85 backdrop-blur-xl dark:border-slate-800/80 dark:bg-slate-950/85">
|
||||
<nav class="contain flex min-h-16 items-center justify-between gap-4" aria-label="Main navigation">
|
||||
<a href="/" class="flex items-center gap-3 font-semibold text-slate-950 dark:text-white" aria-label="Framex home">
|
||||
<span class="grid size-9 place-items-center rounded-lg bg-blue-600 text-sm font-bold text-white shadow-sm shadow-blue-600/30">Fx</span>
|
||||
<span class="text-xl text-black dark:text-white/80 font-extrabold">FramexEngine</span>
|
||||
</a>
|
||||
|
||||
<div class="hidden items-center gap-1 md:flex">
|
||||
<a class="nav-link" href="/#features">Features</a>
|
||||
<a class="nav-link" href="/#markdown">Markdown</a>
|
||||
<a class="nav-link" href="/#themes">Themes</a>
|
||||
<a class="nav-link" href="/about">About</a>
|
||||
<a class="nav-link" href="/docs">Document</a>
|
||||
</div>
|
||||
|
||||
<div class="hidden items-center gap-2 md:flex">
|
||||
<button class="btn btn-ghost size-11 px-0" type="button" data-theme-toggle aria-label="Toggle dark mode" aria-pressed="false">
|
||||
<svg class="size-5 dark:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M19.1 4.9l-1.4 1.4M6.3 17.7l-1.4 1.4M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4"></path>
|
||||
</svg>
|
||||
<svg class="hidden size-5 dark:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<path d="M20 14.5A7.5 7.5 0 0 1 9.5 4 8.5 8.5 0 1 0 20 14.5Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<a class="btn btn-primary" href="/#start">Start building</a>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 md:hidden">
|
||||
<button class="btn btn-ghost size-11 px-0" type="button" data-theme-toggle aria-label="Toggle dark mode" aria-pressed="false">
|
||||
<svg class="size-5 dark:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="4"></circle>
|
||||
<path d="M12 2v2M12 20v2M4 12H2M22 12h-2M19.1 4.9l-1.4 1.4M6.3 17.7l-1.4 1.4M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4"></path>
|
||||
</svg>
|
||||
<svg class="hidden size-5 dark:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<path d="M20 14.5A7.5 7.5 0 0 1 9.5 4 8.5 8.5 0 1 0 20 14.5Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn btn-secondary size-11 px-0" type="button" data-mobile-menu-toggle aria-label="Toggle navigation" aria-expanded="false">
|
||||
<svg class="size-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true">
|
||||
<path d="M4 7h16M4 12h16M4 17h16"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="contain hidden pb-4 md:hidden" data-mobile-menu>
|
||||
<div class="grid gap-1 rounded-lg border border-slate-200 bg-white p-2 shadow-lg shadow-slate-950/10 dark:border-slate-800 dark:bg-slate-900">
|
||||
<a class="nav-link" href="/#features">Features</a>
|
||||
<a class="nav-link" href="/#markdown">Markdown</a>
|
||||
<a class="nav-link" href="/#themes">Themes</a>
|
||||
<a class="nav-link" href="/docs">Docs</a>
|
||||
<a class="btn btn-primary mt-2" href="/#start">Start building</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
Reference in New Issue
Block a user