diff --git a/plugin/src/Branding.php b/plugin/src/Branding.php
new file mode 100644
index 0000000..85b134d
--- /dev/null
+++ b/plugin/src/Branding.php
@@ -0,0 +1,43 @@
+` block because the admin CSP drops inline `style=`.
+ */
+final class Branding
+{
+ /** The plugin's shared status palette (uplifted from the original RAS colours). */
+ public const STATUS_COLORS = [
+ 'open' => '#3d8b40',
+ 'occupied' => '#b8860b',
+ 'dirty' => '#c0392b',
+ 'reserved' => '#2471a3',
+ ];
+
+ /** A page header with the RAS eyebrow, a title, and a one-line subtitle. */
+ public static function head(string $title, string $subtitle, string $nonce): string
+ {
+ return ''
+ . '
'
+ . '
RAS · Restaurant Automation System
'
+ . '
' . self::e($title) . '
'
+ . '
' . self::e($subtitle) . '
'
+ . '
';
+ }
+
+ private static function e(string $v): string
+ {
+ return htmlspecialchars($v, ENT_QUOTES, 'UTF-8');
+ }
+}
diff --git a/plugin/src/KitchenAdmin.php b/plugin/src/KitchenAdmin.php
index 104a6a7..043ad03 100644
--- a/plugin/src/KitchenAdmin.php
+++ b/plugin/src/KitchenAdmin.php
@@ -48,7 +48,7 @@ public function render(string $csrf = '', ?string $notice = null, string $nonce
}
return $this->styles($nonce)
- . 'Tickets on the line. Start a ticket when you begin it, mark it ready when it is up for the pass.
'
. '' . $cols . '
'
diff --git a/plugin/src/OrdersAdmin.php b/plugin/src/OrdersAdmin.php
index 853de82..c523e75 100644
--- a/plugin/src/OrdersAdmin.php
+++ b/plugin/src/OrdersAdmin.php
@@ -46,7 +46,7 @@ public function render(string $csrf = '', ?string $notice = null, ?string $view
$viewId = ($view !== null && preg_match('/^\d+$/', trim($view)) === 1) ? (int) trim($view) : null;
$viewOrder = $viewId !== null ? $this->orders->get($viewId) : null;
- $html = $this->styles($nonce) . 'How service is going — revenue and what is selling. Figures are from settled (paid) orders.
'
. ''
. $this->card('Revenue today', $today['revenue'], $today['orders'] . ' paid')
diff --git a/plugin/src/ReservationsAdmin.php b/plugin/src/ReservationsAdmin.php
index 9694db3..c28673c 100644
--- a/plugin/src/ReservationsAdmin.php
+++ b/plugin/src/ReservationsAdmin.php
@@ -40,7 +40,7 @@ public function render(string $csrf = '', ?string $notice = null, ?string $edit
$filter = ($status !== null && in_array(trim($status), Reservations::STATUSES, true)) ? trim($status) : null;
return $this->styles($nonce)
- . '
Reservations
'
+ . Branding::head('Reservations', 'The book — upcoming bookings.', $nonce)
. $this->notice($notice)
. '
Upcoming bookings. Linking a guest connects the booking to their record in the CRM — opening it needs CRM access.
'
. $this->form($csrf, $editRes)
diff --git a/plugin/src/TablesAdmin.php b/plugin/src/TablesAdmin.php
index da21965..ac55f9e 100644
--- a/plugin/src/TablesAdmin.php
+++ b/plugin/src/TablesAdmin.php
@@ -43,10 +43,10 @@ public function render(string $csrf = '', ?string $notice = null, ?string $edit
$filter = ($status !== null && in_array(trim($status), Tables::STATUSES, true)) ? trim($status) : null;
return $this->styles($nonce)
- . '
Floor
'
+ . Branding::head('Floor', 'The room at a glance — seat, clear and turn tables.', $nonce)
. $this->notice($notice)
- . '
Your tables and their status. Seat guests, send tables for cleaning, and turn them for the next party.
'
. $this->form($csrf, $editTable)
+ . $this->legend()
. $this->filterBar($filter)
. $this->board($csrf, $this->tables->all($filter), $filter);
}
@@ -85,7 +85,21 @@ private function filterBar(?string $active): string
return '
' . $chips . '
';
}
+ /** A key to the status colours — the circular tokens read at a glance. */
+ private function legend(): string
+ {
+ $items = '';
+ foreach (self::STATUS_LABELS as $s => $label) {
+ $items .= '
' . self::e($label) . '';
+ }
+ return '
' . $items . '
';
+ }
+
/**
+ * The floor as a grid of circular table tokens, coloured by status — the RAS
+ * signature, uplifted. The circle links to the table; the seats and the
+ * contextual quick actions sit beneath it.
+ *
* @param list
> $tables
*/
private function board(string $csrf, array $tables, ?string $filter): string
@@ -95,20 +109,18 @@ private function board(string $csrf, array $tables, ?string $filter): string
return '' . $msg . '
';
}
- $cards = '';
+ $tokens = '';
foreach ($tables as $t) {
$status = (string) $t['status'];
- $cards .= ''
- . ''
- . '' . self::e((string) $t['seats']) . ' seats
'
- . '' . $this->actions($csrf, (int) $t['id'], $status) . '
'
+ $tokens .= ''
+ . ''
+ . '' . self::e((string) $t['label']) . ''
+ . '' . self::e((string) $t['seats']) . ' seats · ' . self::e(self::STATUS_LABELS[$status] ?? $status) . '
'
+ . '' . $this->actions($csrf, (int) $t['id'], $status) . '
'
. '';
}
- return '';
+ return '';
}
/** The status quick-actions relevant to a table's current state. */
@@ -166,24 +178,20 @@ private function styles(string $nonce): string
. '.rz-filter{display:flex;flex-wrap:wrap;gap:.4rem;margin:0 0 1rem}'
. '.rz-chip{text-decoration:none;background:rgba(128,128,128,.12);border-radius:999px;padding:.3rem .8rem;font-size:.82rem;color:inherit;min-height:32px;display:inline-flex;align-items:center}'
. '.rz-chip.is-active{background:rgba(52,152,219,.22);color:#2471a3;font-weight:700}'
- . '.rz-board{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(9.5rem,1fr));gap:.75rem}'
- . '.rz-table{border:1px solid rgba(128,128,128,.2);border-left:4px solid rgba(128,128,128,.4);border-radius:10px;padding:.6rem .7rem;display:flex;flex-direction:column;gap:.4rem}'
- . '.rz-status-open{border-left-color:#27ae60}'
- . '.rz-status-occupied{border-left-color:#2980b9}'
- . '.rz-status-dirty{border-left-color:#c0392b}'
- . '.rz-status-reserved{border-left-color:#b8860b}'
- . '.rz-table-top{display:flex;justify-content:space-between;align-items:center;gap:.4rem}'
- . '.rz-table-label{font-weight:800;font-size:1.15rem;text-decoration:none}'
- . '.rz-table-seats{font-size:.8rem;opacity:.7}'
- . '.rz-badge{font-size:.65rem;font-weight:700;padding:.1rem .4rem;border-radius:999px;text-transform:uppercase;letter-spacing:.03em}'
- . '.rz-badge-open{background:rgba(39,174,96,.18);color:#1e8449}'
- . '.rz-badge-occupied{background:rgba(41,128,185,.18);color:#2471a3}'
- . '.rz-badge-dirty{background:rgba(192,57,43,.15);color:#c0392b}'
- . '.rz-badge-reserved{background:rgba(184,134,11,.18);color:#9a7d0a}'
- . '.rz-table-acts{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center;margin-top:.1rem}'
+ . '.rz-status-open{--rs:#3d8b40}.rz-status-occupied{--rs:#b8860b}.rz-status-dirty{--rs:#c0392b}.rz-status-reserved{--rs:#2471a3}'
+ . '.rz-legend{display:flex;flex-wrap:wrap;gap:.9rem;margin:0 0 1rem;font-size:.8rem;color:var(--nb-muted,#6b7280)}'
+ . '.rz-key{display:inline-flex;align-items:center;gap:.4rem}'
+ . '.rz-dot{width:.75rem;height:.75rem;border-radius:50%;background:var(--rs,#888);display:inline-block}'
+ . '.rz-board{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(6.5rem,1fr));gap:1rem .75rem}'
+ . '.rz-token{display:flex;flex-direction:column;align-items:center;gap:.4rem;text-align:center}'
+ . '.rz-circle{width:76px;height:76px;border-radius:50%;background:var(--rs,#888);color:#fff;display:flex;align-items:center;justify-content:center;text-decoration:none;padding:.35rem;box-sizing:border-box;transition:transform .08s ease}'
+ . '.rz-circle:hover{transform:scale(1.05)}'
+ . '.rz-circle-label{font-weight:700;font-size:1.05rem;line-height:1.1;overflow-wrap:anywhere}'
+ . '.rz-token-seats{font-size:.72rem;color:var(--nb-muted,#6b7280);line-height:1.2}'
+ . '.rz-token-acts{display:flex;flex-wrap:wrap;gap:.3rem;align-items:center;justify-content:center}'
. '.rz-act{display:inline}'
- . '.rz-act-btn{min-height:36px;padding:.25rem .6rem;font-size:.8rem}'
- . '.rz-link-danger{background:none;border:0;color:#c0392b;font:inherit;cursor:pointer;text-decoration:underline;padding:.25rem 0;min-height:36px}'
+ . '.rz-act-btn{min-height:32px;padding:.2rem .55rem;font-size:.75rem}'
+ . '.rz-link-danger{background:none;border:0;color:#c0392b;font:inherit;cursor:pointer;text-decoration:underline;padding:.2rem 0;min-height:32px;font-size:.75rem}'
. '';
}
diff --git a/plugin/tests/TablesAdminTest.php b/plugin/tests/TablesAdminTest.php
index 9897a33..b63fb61 100644
--- a/plugin/tests/TablesAdminTest.php
+++ b/plugin/tests/TablesAdminTest.php
@@ -58,7 +58,8 @@ public function test_the_board_shows_status_and_quick_actions(): void
$html = $this->admin->render('CSRF123', null, null, null, 'n');
- self::assertStringContainsString('rz-status-occupied', $html, 'the card is marked with its status');
+ self::assertStringContainsString('rz-status-occupied', $html, 'the token is marked with its status');
+ self::assertStringContainsString('rz-circle', $html, 'tables render as circular tokens (the RAS signature)');
self::assertStringContainsString('action="/admin/restaurant/table-status"', $html, 'quick-action posts to the status action');
// An occupied table offers "Clear" (→ dirty), not "Seat".
self::assertStringContainsString('value="dirty"', $html);