diff --git a/README.md b/README.md index ddb13e3..c7e031f 100644 --- a/README.md +++ b/README.md @@ -895,18 +895,110 @@ Set `data-theme="light"` on `` directly. All component styles react throug --- +## Starting a New App + +### 1. Serve the design system files + +Add an nginx alias so every app on the same host can reference the same files: + +```nginx +# In each app's server block (or a shared include): +location /web_template/ { + alias /path/to/web_template/; + expires 7d; + add_header Cache-Control "public, immutable"; +} +``` + +Then in your HTML: +```html + + +``` + +### 2. Copy the right skeleton + +| Stack | Copy this file | Into your app as | +|-------|---------------|-----------------| +| PHP | `php/layout.php` | `views/layout.php` (or `layout_header.php`) | +| Python/Flask | `python/base.html` | `templates/base.html` | +| Node/Express | `node/middleware.js` + `node/layout.ejs` | `middleware.js` + `views/layout.ejs` | + +### 3. Define your nav + +**PHP** — pass `$navLinks` before including the layout: +```php +$navLinks = [ + ['href' => '/', 'key' => 'dashboard', 'label' => 'Dashboard'], + ['href' => '/reports', 'key' => 'reports', 'label' => 'Reports'], + ['label' => 'Admin', 'key' => 'admin', 'adminOnly' => true, 'children' => [ + ['href' => '/admin/users', 'label' => 'Users'], + ]], +]; +$activeNav = 'dashboard'; +include __DIR__ . '/views/layout.php'; +``` + +**Python/Flask** — inject via context processor or pass directly to `render_template`: +```python +nav_links = [ + {'href': url_for('index'), 'key': 'dashboard', 'label': 'Dashboard'}, + {'href': url_for('settings'), 'key': 'settings', 'label': 'Settings'}, +] +return render_template('page.html', nav_links=nav_links) +``` + +**Node/Express** — set on `res.locals` via `injectLocals` middleware: +```js +app.use((req, res, next) => { + res.locals.navLinks = [ + { href: '/', key: 'dashboard', label: 'Dashboard' }, + { href: '/workers', key: 'workers', label: 'Workers' }, + ]; + next(); +}); +``` + +### 4. Add app-specific CSS + +Create `app.css` in your app. Import nothing from `base.css` — just override tokens or add components: +```css +/* app.css — app-specific extensions only */ +:root { + --app-accent: #FF6B00; /* override if needed */ +} +/* Only put styles here that aren't already in base.css */ +``` + +### 5. Initialise + +In your base template, after `base.js`: +```html + +``` + +--- + ## File Structure ``` web_template/ -├── base.css Design system styles (79 sections, ~5,200 lines) -├── base.js Design system JS (55+ modules, ~2,800 lines) -├── base.html Living reference template -├── README.md This file -└── (framework skeletons) - ├── php/ PHP / Tinker Tickets - ├── python/ Flask / Jinja2 / GANDALF - └── node/ Express / EJS / PULSE +├── base.css Design system styles (79 sections, ~5,200 lines) +├── base.js Design system JS (55+ modules, ~2,800 lines) +├── base.html Living component reference — open in browser to browse everything +├── README.md This file +├── AUTHELIA_INTEGRATION.md Theming Authelia portal with this design system +└── framework skeletons/ + ├── php/ + │ └── layout.php Generic PHP base layout (pass $navLinks) + ├── python/ + │ ├── base.html Jinja2 base template (pass nav_links list) + │ └── auth.py Authelia SSO helper for Flask + └── node/ + ├── middleware.js Express middleware (auth, CSRF, nonce, rate limit) + └── layout.ejs EJS base template (uses res.locals.navLinks) ``` --- diff --git a/node/layout.ejs b/node/layout.ejs new file mode 100644 index 0000000..c1aa757 --- /dev/null +++ b/node/layout.ejs @@ -0,0 +1,146 @@ +<%– + LOTUSGUILD TERMINAL DESIGN SYSTEM — Node.js / Express EJS Base Layout + Extend this in every page template via res.render('page', { ... }). + + Required Express setup (server.js / app.js): + const { requireAuth, cspNonce, injectLocals } = require('./middleware'); + app.use(cspNonce); + app.use(requireAuth); + app.use(injectLocals); + app.set('view engine', 'ejs'); + + Locals injected automatically by middleware.js: + user { username, name, email, groups, isAdmin } + nonce CSP nonce string + appName process.env.APP_NAME + appSubtitle process.env.APP_SUBTITLE + + Locals to set per-route (or via a second res.locals middleware): + pageTitle string — page suffix + activeNav string — must match a navLinks[].key + navLinks array — navigation items: + [{ href: '/path', key: 'mykey', label: 'My Page' }, ...] + Dropdown: + { label: 'Admin', key: 'admin', adminOnly: true, children: [ + { href: '/admin/users', label: 'Users' } + ]} + pageStyles array — optional extra CSS hrefs + pageScripts array — optional extra <script src> paths +–%> +<!DOCTYPE html> +<html lang="en" data-theme="dark"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> + <meta name="theme-color" content="#030508"> + <meta name="robots" content="noindex, nofollow"> + <title><%= pageTitle ? pageTitle + ' — ' : '' %><%= appName || 'LotusGuild' %> + + + + + + + + + + + + <% if (typeof pageStyles !== 'undefined') { pageStyles.forEach(href => { %> + + <% }); } %> + + + + + + + + + +
+
+ +
+ + <%= (appName || 'APP').toUpperCase() %> + + <%= appSubtitle || 'LotusGuild Infrastructure' %> +
+ + +
+ +
+ <% if (user && (user.name || user.username)) { %> + <%= user.name || user.username %> + <% } %> + <% if (user && user.isAdmin) { %> + admin + <% } %> +
+
+ + +
+ <%- body %> +
+ + + + + + + + + + + + + <% if (typeof pageScripts !== 'undefined') { pageScripts.forEach(src => { %> + + <% }); } %> + + +