Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/theme-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Theme CI

# The RAS public theme is plain PHP templates (no build step, no test DB), so CI
# just lints them for syntax on the supported PHP versions.

on:
push:
branches: [nimbus-rebuild, 'slice/**']
paths: ['theme/**', '.github/workflows/theme-ci.yml']
pull_request:
paths: ['theme/**', '.github/workflows/theme-ci.yml']

jobs:
lint:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['8.2', '8.3']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: none
- name: Lint templates
run: |
for f in theme/templates/*.php; do php -l "$f"; done
107 changes: 107 additions & 0 deletions theme/assets/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* RAS — the public face of the Restaurant Automation System.
* A single, committed dark restaurant look (uplifted from the original #222),
* with a warm gold accent and an elegant printed-menu feel.
*/

:root {
--ground: #1a1c20;
--surface: #23262c;
--text: #ecedee;
--muted: #9aa0a8;
--gold: #d4a017;
--gold-bright: #e7b62f;
--rule: rgba(255, 255, 255, 0.09);
--serif: Georgia, "Iowan Old Style", "Times New Roman", serif;
--sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
margin: 0;
background: var(--ground);
color: var(--text);
font-family: var(--sans);
line-height: 1.6;
font-size: 17px;
}

a { color: var(--gold); text-decoration: none; }
a:hover { color: var(--gold-bright); }

img { max-width: 100%; height: auto; }

.wrap { width: 100%; max-width: 900px; margin: 0 auto; padding: 0 1.25rem; }
.measure { max-width: 40rem; }

.skip-link {
position: absolute; left: -999px; top: 0;
background: var(--gold); color: #1a1c20; padding: .6rem 1rem; border-radius: 6px;
}
.skip-link:focus { left: 1rem; top: 1rem; z-index: 10; }

/* Header */
.site-header {
position: sticky; top: 0; z-index: 5;
background: rgba(20, 22, 26, 0.92);
backdrop-filter: saturate(140%) blur(6px);
border-bottom: 1px solid var(--rule);
}
.header-inner { display: flex; align-items: center; justify-content: space-between; min-height: 64px; gap: 1rem; }
.brand { display: inline-flex; align-items: center; gap: .7rem; color: var(--text); }
.brand:hover { color: var(--text); }
.brand-mark {
font-weight: 800; letter-spacing: .12em; font-size: .82rem;
background: var(--gold); color: #1a1c20;
padding: .3rem .5rem; border-radius: 5px;
}
.brand-name { font-family: var(--serif); font-size: 1.15rem; letter-spacing: .01em; }
.site-nav a { font-size: .95rem; letter-spacing: .04em; text-transform: uppercase; }

/* Menu page */
.menu-page { padding: 3rem 0 4rem; }
.menu-head { text-align: center; margin-bottom: 2.75rem; }
.eyebrow {
margin: 0 0 .6rem; font-size: .72rem; font-weight: 700;
letter-spacing: .22em; text-transform: uppercase; color: var(--gold);
}
.menu-head h1 { font-family: var(--serif); font-size: 2.6rem; font-weight: 400; margin: 0 0 .5rem; }
.lede { color: var(--muted); margin: 0; }

.menu-group { margin: 0 0 2.75rem; }
.menu-group h2 {
font-family: var(--serif); font-weight: 400; font-size: 1.5rem;
margin: 0 0 1.1rem; padding-bottom: .5rem; border-bottom: 1px solid var(--rule);
color: var(--text);
}
.menu-list { list-style: none; margin: 0; padding: 0; }
.menu-item { padding: .7rem 0; border-bottom: 1px dashed rgba(255, 255, 255, 0.05); }
.menu-item:last-child { border-bottom: 0; }
.menu-row { display: flex; align-items: baseline; gap: .35rem; }
.menu-name { font-weight: 500; }
.menu-leader { flex: 1; border-bottom: 1px dotted rgba(255, 255, 255, 0.28); transform: translateY(-.25rem); }
.menu-price { font-variant-numeric: tabular-nums; color: var(--gold); font-weight: 600; white-space: nowrap; }
.menu-desc { margin: .3rem 0 0; color: var(--muted); font-size: .92rem; max-width: 46ch; }
.empty { color: var(--muted); text-align: center; padding: 2rem 0; }

/* Generic page + 404 */
.page { padding: 3rem 0 4rem; }
.page h1 { font-family: var(--serif); font-weight: 400; font-size: 2.2rem; margin: 0 0 1rem; }
.prose { color: var(--text); }
.notfound { text-align: center; }

/* Footer */
.site-footer { border-top: 1px solid var(--rule); padding: 2.5rem 0; margin-top: 2rem; }
.footer-inner { text-align: center; }
.footer-name { font-family: var(--serif); font-size: 1.15rem; margin: 0 0 .3rem; }
.footer-meta { color: var(--muted); margin: 0 0 .6rem; }
.footer-fine { color: var(--muted); font-size: .8rem; margin: 0; opacity: .8; }

@media (max-width: 40rem) {
.menu-head h1 { font-size: 2.1rem; }
.brand-name { display: none; }
body { font-size: 16px; }
}
15 changes: 15 additions & 0 deletions theme/templates/404.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

/**
* Themed not-found page.
*
* @var callable $e
*/
?>
<section class="page notfound">
<div class="wrap measure">
<p class="eyebrow">RAS</p>
<h1>Not on the menu</h1>
<p class="lede">That page isn&rsquo;t here. Have a look at <a href="/menu_items">the menu</a> instead.</p>
</div>
</section>
57 changes: 57 additions & 0 deletions theme/templates/collection-menu_items.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/**
* The guest menu (/menu_items) — live menu items grouped by their `category`
* relation, priced. The relation is expanded by EntryView, so each item's
* `fields.category` is a list of `{id,slug,title}`.
*
* @var array{handle:string,name:string} $collection
* @var list<array<string,mixed>> $entries
* @var callable $e
*/
$groups = [];
foreach ($entries as $item) {
$fields = is_array($item['fields'] ?? null) ? $item['fields'] : [];
$rel = $fields['category'] ?? null;
$cat = is_array($rel) && isset($rel[0]['title']) && (string) $rel[0]['title'] !== ''
? (string) $rel[0]['title']
: 'More';
$groups[$cat][] = $item;
}
?>
<section class="menu-page">
<div class="wrap">
<header class="menu-head">
<p class="eyebrow">RAS · Menu</p>
<h1>What&rsquo;s on</h1>
<p class="lede">Fresh every service. Prices include tax.</p>
</header>

<?php if ($entries === []): ?>
<p class="empty">The menu is being set. Please check back shortly.</p>
<?php endif; ?>

<?php foreach ($groups as $cat => $items): ?>
<section class="menu-group">
<h2><?= $e($cat) ?></h2>
<ul class="menu-list">
<?php foreach ($items as $item): ?>
<?php
$fields = is_array($item['fields'] ?? null) ? $item['fields'] : [];
$price = $fields['price'] ?? null;
$desc = trim((string) ($fields['body'] ?? ''));
?>
<li class="menu-item">
<div class="menu-row">
<span class="menu-name"><?= $e((string) $item['title']) ?></span>
<span class="menu-leader" aria-hidden="true"></span>
<span class="menu-price"><?= $price !== null && $price !== '' ? '&pound;' . $e(number_format((float) $price, 2)) : '' ?></span>
</div>
<?php if ($desc !== ''): ?><p class="menu-desc"><?= $e($desc) ?></p><?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
</section>
<?php endforeach; ?>
</div>
</section>
17 changes: 17 additions & 0 deletions theme/templates/entry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* A single info page (an `entry`), rendered simply inside the RAS shell.
*
* @var array<string,mixed> $entry
* @var callable $e
*/
$fields = is_array($entry['fields'] ?? null) ? $entry['fields'] : [];
$body = trim((string) ($fields['body'] ?? ''));
?>
<article class="page">
<div class="wrap measure">
<h1><?= $e((string) ($entry['title'] ?? '')) ?></h1>
<?php if ($body !== ''): ?><div class="prose"><?= nl2br($e($body)) ?></div><?php endif; ?>
</div>
</article>
17 changes: 17 additions & 0 deletions theme/templates/footer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

/**
* The public footer.
*
* @var string $appName the site name
* @var callable $e escape a value for output
*/
$year = date('Y');
?>
<footer class="site-footer">
<div class="wrap footer-inner">
<p class="footer-name"><?= $e($appName) ?></p>
<p class="footer-meta">Open daily · Lunch &amp; dinner</p>
<p class="footer-fine">Powered by the Restaurant Automation System on NimbusCMS · <?= $e((string) $year) ?></p>
</div>
</footer>
20 changes: 20 additions & 0 deletions theme/templates/header.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/**
* The public header — the RAS wordmark and the menu link.
*
* @var string $appName the site name
* @var callable $e escape a value for output
*/
?>
<header class="site-header">
<div class="wrap header-inner">
<a class="brand" href="/">
<span class="brand-mark">RAS</span>
<span class="brand-name"><?= $e($appName) ?></span>
</a>
<nav class="site-nav" aria-label="Primary">
<a href="/menu_items">Menu</a>
</nav>
</div>
</header>
44 changes: 44 additions & 0 deletions theme/templates/layout.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

/**
* The HTML shell for the public RAS site.
*
* @var string $appName the site name
* @var string $__content the rendered page
* @var string $title the page title (optional)
* @var callable $partial include another theme template
* @var callable $e escape a value for output
* @var array<string,mixed> $meta
* @var string $head extra <head> HTML contributed by plugins (already-rendered, trusted)
*/
$pageTitle = isset($title) && $title !== '' ? $title . ' · ' . $appName : $appName;
$meta = $meta ?? [];
$cssVer = substr((string) @hash_file('crc32b', __DIR__ . '/../assets/app.css'), 0, 8);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= $e($pageTitle) ?></title>
<?php if (!empty($meta['description'])): ?>
<meta name="description" content="<?= $e($meta['description']) ?>">
<?php endif; ?>
<?php if (!empty($meta['canonical'])): ?>
<link rel="canonical" href="<?= $e($meta['canonical']) ?>">
<?php endif; ?>
<meta property="og:site_name" content="<?= $e($appName) ?>">
<meta property="og:title" content="<?= $e($pageTitle) ?>">
<meta property="og:type" content="<?= $e($meta['og_type'] ?? 'website') ?>">
<?= $head ?? '' ?>
<link rel="stylesheet" href="/theme/assets/app.css?v=<?= $e($cssVer) ?>">
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<?= $partial('header') ?>
<main id="main">
<?= $__content ?>
</main>
<?= $partial('footer') ?>
</body>
</html>
16 changes: 16 additions & 0 deletions theme/theme.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "RAS",
"version": "0.1.0",
"description": "The public face of the Restaurant Automation System — a dark, elegant guest menu carrying the RAS identity. Plain PHP templates, one stylesheet, no build step.",
"nav": ["menu_items"],
"templates": {
"layout": "HTML shell; includes header and footer.",
"header": "Site header with the RAS wordmark and the menu link.",
"footer": "Footer with hours and contact.",
"collection-menu_items": "The guest menu (the `menu_items` collection), grouped by category with prices.",
"entry": "A single info page.",
"404": "Themed not-found page."
},
"specialization": "collection-menu_items renders the `menu_items` collection provisioned by the restaurant app — each item has a `price` number and a `category` relation (expanded). Grouped by category name, priced, with dotted leaders.",
"assets": "Files under assets/ are served at /theme/assets/<path> (e.g. assets/app.css -> /theme/assets/app.css)."
}
Loading