- Implemented the dark photo-based theme.
Moved all images to public/assets/images. Added darkened photography across heroes, specials, menu cards, authentication, story, checkout, and footer. Replaced placeholder branding with the supplied logo. Preserved configurable menu item images. Updated desktop and mobile styling for readable cream/gold text.
@@ -0,0 +1,12 @@
|
||||
RewriteEngine On
|
||||
|
||||
# When public/ is the DocumentRoot, allow its own .htaccess to handle the request.
|
||||
RewriteRule ^public/ - [L]
|
||||
|
||||
# Never expose application internals when the repository root is public.
|
||||
RewriteRule ^(?:app|config|storage|views)(?:/|$) - [F,END,NC]
|
||||
|
||||
# Serve public assets directly, then send every application URL to PHP.
|
||||
RewriteRule ^assets/(.*)$ public/assets/$1 [END,NC]
|
||||
RewriteRule ^favicon\.ico$ public/favicon.ico [END,NC]
|
||||
RewriteRule ^ public/index.php [QSA,END]
|
||||
@@ -45,6 +45,29 @@ Before public use, enter the restaurant's verified phone number, email address,
|
||||
and hours in **Dashboard > Settings**. They are intentionally not invented in
|
||||
the default configuration.
|
||||
|
||||
## Apache Routing
|
||||
|
||||
The repository includes two rewrite configurations:
|
||||
|
||||
- `public/.htaccess` when the Apache `DocumentRoot` points to `public/`
|
||||
- `.htaccess` when shared hosting exposes the repository directory itself
|
||||
|
||||
Apache must have `mod_rewrite` enabled and must permit `.htaccess` overrides.
|
||||
A typical virtual host uses:
|
||||
|
||||
```apache
|
||||
DocumentRoot /path/to/fatbottomgrille/public
|
||||
|
||||
<Directory /path/to/fatbottomgrille>
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
```
|
||||
|
||||
The preferred setup is to point the web root at `public/`. If the application
|
||||
is installed in a URL subdirectory, generated links, form actions, redirects,
|
||||
and assets automatically include that subdirectory.
|
||||
|
||||
## Initial Administrator
|
||||
|
||||
- Email: `admin@fatbottomgrille.com`
|
||||
|
||||
@@ -8,7 +8,7 @@ final class Http
|
||||
{
|
||||
public static function redirect(string $path): never
|
||||
{
|
||||
header('Location: ' . $path);
|
||||
header('Location: ' . self::url($path));
|
||||
exit;
|
||||
}
|
||||
|
||||
@@ -46,8 +46,60 @@ final class Http
|
||||
public static function path(): string
|
||||
{
|
||||
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||||
$path = is_string($path) ? rtrim($path, '/') : '/';
|
||||
$path = is_string($path) ? rawurldecode($path) : '/';
|
||||
$basePath = self::basePath();
|
||||
|
||||
if ($basePath !== '' && ($path === $basePath || str_starts_with($path, $basePath . '/'))) {
|
||||
$path = substr($path, strlen($basePath)) ?: '/';
|
||||
}
|
||||
|
||||
$path = '/' . ltrim($path, '/');
|
||||
$path = rtrim($path, '/');
|
||||
return $path === '' ? '/' : $path;
|
||||
}
|
||||
}
|
||||
|
||||
public static function url(string $path = '/'): string
|
||||
{
|
||||
if (
|
||||
preg_match('#^(?:[a-z][a-z0-9+.-]*:)?//#i', $path)
|
||||
|| str_starts_with($path, 'mailto:')
|
||||
|| str_starts_with($path, 'tel:')
|
||||
|| str_starts_with($path, '#')
|
||||
) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
$basePath = self::basePath();
|
||||
$path = '/' . ltrim($path, '/');
|
||||
return ($basePath === '' ? '' : $basePath) . ($path === '/' ? '/' : $path);
|
||||
}
|
||||
|
||||
public static function basePath(): string
|
||||
{
|
||||
static $basePath;
|
||||
if (isset($basePath)) {
|
||||
return $basePath;
|
||||
}
|
||||
|
||||
$documentRoot = realpath((string) ($_SERVER['DOCUMENT_ROOT'] ?? ''));
|
||||
$applicationRoot = realpath(BASE_PATH);
|
||||
$publicRoot = realpath(BASE_PATH . '/public');
|
||||
|
||||
if ($documentRoot && $publicRoot && $documentRoot === $publicRoot) {
|
||||
return $basePath = '';
|
||||
}
|
||||
|
||||
if (
|
||||
$documentRoot
|
||||
&& $applicationRoot
|
||||
&& str_starts_with($applicationRoot . DIRECTORY_SEPARATOR, rtrim($documentRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR)
|
||||
) {
|
||||
$relative = substr($applicationRoot, strlen(rtrim($documentRoot, DIRECTORY_SEPARATOR)));
|
||||
return $basePath = rtrim('/' . trim(str_replace(DIRECTORY_SEPARATOR, '/', $relative), '/'), '/');
|
||||
}
|
||||
|
||||
$script = str_replace('\\', '/', (string) ($_SERVER['SCRIPT_NAME'] ?? ''));
|
||||
$script = preg_replace('#/(?:public/)?index\.php$#', '', $script) ?? '';
|
||||
return $basePath = rtrim($script, '/');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,20 @@ function csrf_field(): string
|
||||
|
||||
function active_path(string $path): string
|
||||
{
|
||||
$current = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||||
$current = \FatBottom\Core\Http::path();
|
||||
return $current === $path ? 'is-active' : '';
|
||||
}
|
||||
|
||||
function url(string $path = '/'): string
|
||||
{
|
||||
return \FatBottom\Core\Http::url($path);
|
||||
}
|
||||
|
||||
function asset(string $path): string
|
||||
{
|
||||
return \FatBottom\Core\Http::url('/' . ltrim($path, '/'));
|
||||
}
|
||||
|
||||
function selected(mixed $value, mixed $expected): string
|
||||
{
|
||||
return (string) $value === (string) $expected ? 'selected' : '';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
RewriteEngine On
|
||||
|
||||
# Existing public files are served normally; all other URLs use the app router.
|
||||
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^ - [END]
|
||||
|
||||
RewriteRule ^ index.php [QSA,END]
|
||||
@@ -31,8 +31,8 @@ html {
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background: var(--paper);
|
||||
color: var(--cream);
|
||||
background: var(--ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
@@ -41,8 +41,8 @@ body {
|
||||
|
||||
body.store-body {
|
||||
background-image:
|
||||
radial-gradient(circle at 0 0, rgba(200, 151, 59, 0.06), transparent 28%),
|
||||
linear-gradient(rgba(8, 18, 28, 0.018) 1px, transparent 1px);
|
||||
radial-gradient(circle at 0 0, rgba(200, 151, 59, 0.08), transparent 28%),
|
||||
linear-gradient(rgba(255, 255, 255, 0.018) 1px, transparent 1px);
|
||||
background-size: auto, 100% 22px;
|
||||
}
|
||||
|
||||
@@ -297,6 +297,14 @@ h3 {
|
||||
font-size: 1rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.02em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.brand-mark img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.brand-copy,
|
||||
@@ -401,7 +409,8 @@ h3 {
|
||||
overflow: hidden;
|
||||
color: var(--cream);
|
||||
background:
|
||||
linear-gradient(108deg, rgba(8, 18, 28, 0.98) 0%, rgba(12, 30, 43, 0.96) 52%, rgba(16, 42, 58, 0.9) 100%);
|
||||
linear-gradient(90deg, rgba(5, 12, 18, 0.99) 0%, rgba(8, 20, 30, 0.96) 48%, rgba(8, 18, 28, 0.76) 100%),
|
||||
url("images/reuben-fries.jpg") center 48% / cover no-repeat;
|
||||
}
|
||||
|
||||
.hero::after {
|
||||
@@ -495,18 +504,61 @@ h3 {
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.plate-ring {
|
||||
display: grid;
|
||||
width: min(470px, 40vw);
|
||||
aspect-ratio: 1;
|
||||
place-items: center;
|
||||
border: 2px solid rgba(225, 183, 94, 0.35);
|
||||
border-radius: 50%;
|
||||
.hero-food-photo {
|
||||
position: relative;
|
||||
width: min(460px, 39vw);
|
||||
aspect-ratio: 0.88;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(225, 183, 94, 0.42);
|
||||
border-radius: 44% 44% 8px 8px;
|
||||
box-shadow: 0 45px 90px rgba(0, 0, 0, 0.52);
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.hero-food-photo::after {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(circle, rgba(255, 255, 255, 0.11) 0 52%, rgba(255, 255, 255, 0.03) 53% 65%, transparent 66%),
|
||||
linear-gradient(145deg, rgba(255, 255, 255, 0.08), transparent 65%);
|
||||
box-shadow: 0 45px 90px rgba(0, 0, 0, 0.35);
|
||||
transform: rotate(-6deg);
|
||||
linear-gradient(180deg, rgba(5, 12, 18, 0.06), rgba(5, 12, 18, 0.22) 54%, rgba(5, 12, 18, 0.94)),
|
||||
linear-gradient(90deg, rgba(200, 151, 59, 0.08), transparent);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.hero-food-photo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: 48% center;
|
||||
filter: saturate(0.82) contrast(1.06) brightness(0.72);
|
||||
}
|
||||
|
||||
.hero-photo-caption {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 28px;
|
||||
bottom: 25px;
|
||||
left: 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid rgba(225, 183, 94, 0.48);
|
||||
}
|
||||
|
||||
.hero-photo-caption span {
|
||||
color: var(--gold-light);
|
||||
font-size: 0.62rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hero-photo-caption strong {
|
||||
margin-top: 4px;
|
||||
color: var(--cream);
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.35rem;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.burger {
|
||||
@@ -646,13 +698,37 @@ h3 {
|
||||
padding: 30px;
|
||||
overflow: hidden;
|
||||
color: var(--cream);
|
||||
background:
|
||||
linear-gradient(145deg, rgba(255, 255, 255, 0.07), transparent 58%),
|
||||
var(--ink-soft);
|
||||
background-color: var(--ink-soft);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border: 1px solid rgba(200, 151, 59, 0.28);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.special-card:nth-child(4n + 1) {
|
||||
background-image:
|
||||
linear-gradient(112deg, rgba(5, 12, 18, 0.98), rgba(8, 18, 28, 0.82)),
|
||||
url("images/quesadilla.jpg");
|
||||
}
|
||||
|
||||
.special-card:nth-child(4n + 2) {
|
||||
background-image:
|
||||
linear-gradient(112deg, rgba(5, 12, 18, 0.98), rgba(8, 18, 28, 0.8)),
|
||||
url("images/sausage-kale-soup.jpg");
|
||||
}
|
||||
|
||||
.special-card:nth-child(4n + 3) {
|
||||
background-image:
|
||||
linear-gradient(112deg, rgba(5, 12, 18, 0.98), rgba(8, 18, 28, 0.78)),
|
||||
url("images/grilled-chicken-salad.jpg");
|
||||
}
|
||||
|
||||
.special-card:nth-child(4n + 4) {
|
||||
background-image:
|
||||
linear-gradient(112deg, rgba(5, 12, 18, 0.98), rgba(8, 18, 28, 0.8)),
|
||||
url("images/curry-rice.jpg");
|
||||
}
|
||||
|
||||
.special-card::after {
|
||||
position: absolute;
|
||||
right: -70px;
|
||||
@@ -707,7 +783,9 @@ h3 {
|
||||
}
|
||||
|
||||
.menu-preview {
|
||||
background: var(--paper);
|
||||
background:
|
||||
radial-gradient(circle at 86% 14%, rgba(200, 151, 59, 0.08), transparent 24%),
|
||||
#09141e;
|
||||
}
|
||||
|
||||
.menu-grid {
|
||||
@@ -719,8 +797,9 @@ h3 {
|
||||
.menu-card {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background: var(--white);
|
||||
border: 1px solid #dedcd4;
|
||||
color: var(--cream);
|
||||
background: #0d1c28;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: var(--radius-large);
|
||||
box-shadow: 0 8px 28px rgba(8, 18, 28, 0.06);
|
||||
transition: transform 160ms ease, box-shadow 160ms ease;
|
||||
@@ -750,6 +829,53 @@ h3 {
|
||||
letter-spacing: -0.06em;
|
||||
}
|
||||
|
||||
.menu-card:nth-child(7n + 1) .menu-card-art:not(.has-image) {
|
||||
background-image:
|
||||
linear-gradient(rgba(5, 12, 18, 0.62), rgba(5, 12, 18, 0.86)),
|
||||
url("images/reuben-fries.jpg");
|
||||
}
|
||||
|
||||
.menu-card:nth-child(7n + 2) .menu-card-art:not(.has-image) {
|
||||
background-image:
|
||||
linear-gradient(rgba(5, 12, 18, 0.62), rgba(5, 12, 18, 0.86)),
|
||||
url("images/chicken-sandwich-fries.jpg");
|
||||
}
|
||||
|
||||
.menu-card:nth-child(7n + 3) .menu-card-art:not(.has-image) {
|
||||
background-image:
|
||||
linear-gradient(rgba(5, 12, 18, 0.62), rgba(5, 12, 18, 0.86)),
|
||||
url("images/quesadilla.jpg");
|
||||
}
|
||||
|
||||
.menu-card:nth-child(7n + 4) .menu-card-art:not(.has-image) {
|
||||
background-image:
|
||||
linear-gradient(rgba(5, 12, 18, 0.62), rgba(5, 12, 18, 0.86)),
|
||||
url("images/curry-rice.jpg");
|
||||
}
|
||||
|
||||
.menu-card:nth-child(7n + 5) .menu-card-art:not(.has-image) {
|
||||
background-image:
|
||||
linear-gradient(rgba(5, 12, 18, 0.62), rgba(5, 12, 18, 0.86)),
|
||||
url("images/italian-sub.jpg");
|
||||
}
|
||||
|
||||
.menu-card:nth-child(7n + 6) .menu-card-art:not(.has-image) {
|
||||
background-image:
|
||||
linear-gradient(rgba(5, 12, 18, 0.62), rgba(5, 12, 18, 0.86)),
|
||||
url("images/sausage-kale-soup.jpg");
|
||||
}
|
||||
|
||||
.menu-card:nth-child(7n) .menu-card-art:not(.has-image) {
|
||||
background-image:
|
||||
linear-gradient(rgba(5, 12, 18, 0.62), rgba(5, 12, 18, 0.86)),
|
||||
url("images/grilled-chicken-salad.jpg");
|
||||
}
|
||||
|
||||
.menu-card-art:not(.has-image) {
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.menu-card-art::after {
|
||||
position: absolute;
|
||||
width: 150px;
|
||||
@@ -798,14 +924,14 @@ h3 {
|
||||
}
|
||||
|
||||
.menu-card-heading > strong {
|
||||
color: #8e6422;
|
||||
color: var(--gold-light);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.menu-card-body > p {
|
||||
min-height: 50px;
|
||||
margin: 12px 0 16px;
|
||||
color: #68737a;
|
||||
color: #aebbc3;
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -819,8 +945,8 @@ h3 {
|
||||
|
||||
.tag-list span {
|
||||
padding: 3px 8px;
|
||||
color: #5f6b72;
|
||||
background: #f0eee8;
|
||||
color: #b9c4ca;
|
||||
background: #152938;
|
||||
border-radius: 20px;
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
@@ -836,9 +962,10 @@ h3 {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
padding-inline: 10px;
|
||||
border: 1px solid #cbc9c1;
|
||||
color: var(--cream);
|
||||
border: 1px solid #344857;
|
||||
border-radius: var(--radius);
|
||||
background: var(--white);
|
||||
background: #08141f;
|
||||
}
|
||||
|
||||
.category-band {
|
||||
@@ -908,7 +1035,9 @@ h3 {
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
color: var(--gold);
|
||||
background: var(--navy);
|
||||
background:
|
||||
linear-gradient(rgba(5, 12, 18, 0.58), rgba(5, 12, 18, 0.9)),
|
||||
url("images/italian-sub.jpg") center / cover no-repeat;
|
||||
}
|
||||
|
||||
.story-number {
|
||||
@@ -939,8 +1068,8 @@ h3 {
|
||||
padding-block: 72px;
|
||||
color: var(--cream);
|
||||
background:
|
||||
radial-gradient(circle at 80% 0, rgba(200, 151, 59, 0.18), transparent 30%),
|
||||
var(--navy);
|
||||
linear-gradient(90deg, rgba(5, 12, 18, 0.97), rgba(8, 18, 28, 0.8)),
|
||||
url("images/italian-sub.jpg") center 55% / cover no-repeat;
|
||||
}
|
||||
|
||||
.page-hero-compact {
|
||||
@@ -1812,7 +1941,10 @@ textarea:focus {
|
||||
.site-footer {
|
||||
padding-top: 68px;
|
||||
color: #b6c0c6;
|
||||
background: #050c12;
|
||||
background:
|
||||
linear-gradient(90deg, rgba(5, 12, 18, 0.99), rgba(5, 12, 18, 0.94)),
|
||||
url("images/fat-bottom-grille-logo.jpg") right -80px center / 520px no-repeat,
|
||||
#050c12;
|
||||
}
|
||||
|
||||
.footer-grid {
|
||||
@@ -1857,6 +1989,156 @@ textarea:focus {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Dark storefront surfaces */
|
||||
.store-body main {
|
||||
background:
|
||||
radial-gradient(circle at 10% 18%, rgba(200, 151, 59, 0.055), transparent 25%),
|
||||
#09141e;
|
||||
}
|
||||
|
||||
.store-body .button-outline {
|
||||
color: var(--cream);
|
||||
border-color: #788892;
|
||||
}
|
||||
|
||||
.store-body .button-outline:hover {
|
||||
color: var(--ink);
|
||||
background: var(--gold);
|
||||
border-color: var(--gold);
|
||||
}
|
||||
|
||||
.store-body .section-heading > p,
|
||||
.store-body .story-grid p,
|
||||
.store-body .menu-category-heading p,
|
||||
.store-body .empty-state p,
|
||||
.store-body .cart-item-copy p,
|
||||
.store-body .cart-item-copy span,
|
||||
.store-body .form-section-heading p,
|
||||
.store-body .demo-payment p,
|
||||
.store-body .account-email,
|
||||
.store-body .order-history article > div span,
|
||||
.store-body .auth-switch,
|
||||
.store-body .form-footnote {
|
||||
color: #9eacb5;
|
||||
}
|
||||
|
||||
.store-body .story-section,
|
||||
.store-body .menu-page,
|
||||
.store-body .checkout-layout,
|
||||
.store-body .account-grid {
|
||||
color: var(--cream);
|
||||
}
|
||||
|
||||
.store-body .story-section {
|
||||
background:
|
||||
linear-gradient(90deg, rgba(8, 18, 28, 0.99), rgba(8, 18, 28, 0.92)),
|
||||
url("images/sausage-kale-soup.jpg") right center / 46% auto no-repeat;
|
||||
}
|
||||
|
||||
.store-body .category-band {
|
||||
background:
|
||||
linear-gradient(90deg, rgba(5, 12, 18, 0.98), rgba(5, 12, 18, 0.86)),
|
||||
url("images/curry-rice.jpg") center 56% / cover no-repeat;
|
||||
}
|
||||
|
||||
.store-body .menu-category-heading {
|
||||
border-bottom-color: rgba(225, 183, 94, 0.34);
|
||||
}
|
||||
|
||||
.store-body .panel,
|
||||
.store-body .cart-list,
|
||||
.store-body .empty-state,
|
||||
.store-body .auth-card {
|
||||
color: var(--cream);
|
||||
background: #0d1c28;
|
||||
border-color: rgba(255, 255, 255, 0.11);
|
||||
box-shadow: 0 16px 42px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.store-body .cart-item,
|
||||
.store-body .form-section,
|
||||
.store-body .order-history article {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.store-body label {
|
||||
color: #b8c3ca;
|
||||
}
|
||||
|
||||
.store-body input,
|
||||
.store-body select,
|
||||
.store-body textarea {
|
||||
color: var(--cream);
|
||||
background: #07131d;
|
||||
border-color: #344653;
|
||||
}
|
||||
|
||||
.store-body input::placeholder,
|
||||
.store-body textarea::placeholder {
|
||||
color: #71818c;
|
||||
}
|
||||
|
||||
.store-body .demo-payment,
|
||||
.store-body .notice,
|
||||
.store-body .check-control,
|
||||
.store-body .account-password {
|
||||
color: #b8c3ca;
|
||||
background: #0a1722;
|
||||
border-color: #2d404e;
|
||||
}
|
||||
|
||||
.store-body .check-control small {
|
||||
color: #84939c;
|
||||
}
|
||||
|
||||
.store-body .notice-error {
|
||||
color: #efb1b1;
|
||||
background: #2a171b;
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.store-body .order-summary {
|
||||
background:
|
||||
linear-gradient(145deg, rgba(8, 18, 28, 0.98), rgba(8, 18, 28, 0.88)),
|
||||
url("images/quesadilla.jpg") center / cover no-repeat;
|
||||
border: 1px solid rgba(200, 151, 59, 0.22);
|
||||
}
|
||||
|
||||
.store-body .auth-section {
|
||||
background:
|
||||
linear-gradient(90deg, rgba(5, 12, 18, 0.97), rgba(8, 18, 28, 0.87)),
|
||||
url("images/grilled-chicken-salad.jpg") center / cover no-repeat;
|
||||
}
|
||||
|
||||
.store-body .confirmation-section {
|
||||
background:
|
||||
linear-gradient(rgba(5, 12, 18, 0.94), rgba(5, 12, 18, 0.97)),
|
||||
url("images/sausage-kale-soup.jpg") center / cover no-repeat;
|
||||
}
|
||||
|
||||
.store-body .status-pill {
|
||||
color: #c2ccd2;
|
||||
background: #182a37;
|
||||
}
|
||||
|
||||
.store-body .status-paid,
|
||||
.store-body .status-completed,
|
||||
.store-body .status-ready {
|
||||
color: #9de0c0;
|
||||
background: #17382d;
|
||||
}
|
||||
|
||||
.store-body .status-preparing {
|
||||
color: #f0ce83;
|
||||
background: #3b3019;
|
||||
}
|
||||
|
||||
.store-body .status-cancelled,
|
||||
.store-body .status-refunded {
|
||||
color: #efaaaa;
|
||||
background: #3a2024;
|
||||
}
|
||||
|
||||
/* Dashboard */
|
||||
.admin-body {
|
||||
color: #dbe2e6;
|
||||
@@ -2769,7 +3051,7 @@ code {
|
||||
margin-top: -50px;
|
||||
}
|
||||
|
||||
.plate-ring {
|
||||
.hero-food-photo {
|
||||
width: min(470px, 78vw);
|
||||
}
|
||||
|
||||
@@ -2904,8 +3186,8 @@ code {
|
||||
min-height: 330px;
|
||||
}
|
||||
|
||||
.burger {
|
||||
height: 220px;
|
||||
.hero-food-photo {
|
||||
width: min(430px, 86vw);
|
||||
}
|
||||
|
||||
.hero-stamp {
|
||||
|
||||
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 469 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 200 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 80 KiB |
@@ -1,10 +1,53 @@
|
||||
<?php
|
||||
|
||||
$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
|
||||
$file = __DIR__ . $path;
|
||||
if ($path !== '/' && is_file($file)) {
|
||||
$path = is_string($path) ? $path : '/';
|
||||
$documentRoot = realpath((string) ($_SERVER['DOCUMENT_ROOT'] ?? ''));
|
||||
$applicationRoot = realpath(dirname(__DIR__));
|
||||
$publicRoot = realpath(__DIR__);
|
||||
$basePath = '';
|
||||
|
||||
if ($documentRoot && $publicRoot && $documentRoot !== $publicRoot && $applicationRoot) {
|
||||
if ($documentRoot === $applicationRoot) {
|
||||
$basePath = '';
|
||||
} elseif (str_starts_with($applicationRoot, rtrim($documentRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR)) {
|
||||
$basePath = '/' . trim(substr($applicationRoot, strlen($documentRoot)), DIRECTORY_SEPARATOR);
|
||||
}
|
||||
}
|
||||
|
||||
$publicPath = $path;
|
||||
if ($basePath !== '' && ($publicPath === $basePath || str_starts_with($publicPath, $basePath . '/'))) {
|
||||
$publicPath = substr($publicPath, strlen($basePath)) ?: '/';
|
||||
}
|
||||
|
||||
$file = realpath(__DIR__ . '/' . ltrim($publicPath, '/'));
|
||||
$isPublicFile = $file
|
||||
&& $publicRoot
|
||||
&& str_starts_with($file, rtrim($publicRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR)
|
||||
&& is_file($file);
|
||||
|
||||
if ($publicPath !== '/' && $isPublicFile) {
|
||||
if ($documentRoot === $publicRoot) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$contentTypes = [
|
||||
'css' => 'text/css; charset=UTF-8',
|
||||
'js' => 'application/javascript; charset=UTF-8',
|
||||
'json' => 'application/json; charset=UTF-8',
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif',
|
||||
'svg' => 'image/svg+xml',
|
||||
'webp' => 'image/webp',
|
||||
'ico' => 'image/x-icon',
|
||||
];
|
||||
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
header('Content-Type: ' . ($contentTypes[$extension] ?? 'application/octet-stream'));
|
||||
header('Content-Length: ' . filesize($file));
|
||||
readfile($file);
|
||||
exit;
|
||||
}
|
||||
|
||||
require __DIR__ . '/index.php';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
<span class="account-email"><?= e($currentUser['email']) ?></span>
|
||||
</div>
|
||||
<form class="form-stack" action="/account" method="post">
|
||||
<form class="form-stack" action="<?= e(url('/account')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="form-grid">
|
||||
<label>Full name
|
||||
@@ -68,7 +68,7 @@
|
||||
<span class="eyebrow">Past pickups</span>
|
||||
<h2>Order History</h2>
|
||||
</div>
|
||||
<a class="button button-small" href="/menu">Order Again</a>
|
||||
<a class="button button-small" href="<?= e(url('/menu')) ?>">Order Again</a>
|
||||
</div>
|
||||
<?php if (!$orders): ?>
|
||||
<div class="empty-state compact">
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<?php if (password_verify('ChangeMe123!', (string) $currentUser['password_hash'])): ?>
|
||||
<div class="notice admin-security-notice">
|
||||
<strong>Security reminder:</strong> the initial administrator password is still active.
|
||||
Change it from <a href="/account">My Account</a> before deployment.
|
||||
Change it from <a href="<?= e(url('/account')) ?>">My Account</a> before deployment.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<span class="eyebrow">Live queue</span>
|
||||
<h2>Recent Orders</h2>
|
||||
</div>
|
||||
<a class="button button-small" href="/admin/orders">View All Orders</a>
|
||||
<a class="button button-small" href="<?= e(url('/admin/orders')) ?>">View All Orders</a>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
@@ -46,7 +46,7 @@
|
||||
<?php endif; ?>
|
||||
<?php foreach ($recentOrders as $order): ?>
|
||||
<tr>
|
||||
<td><a href="/admin/order?id=<?= (int) $order['id'] ?>"><?= e($order['order_number']) ?></a></td>
|
||||
<td><a href="<?= e(url('/admin/order')) ?>?id=<?= (int) $order['id'] ?>"><?= e($order['order_number']) ?></a></td>
|
||||
<td><?= e($order['customer_name']) ?><small><?= e($order['customer_phone']) ?></small></td>
|
||||
<td><?= e(store_datetime((string) $order['placed_at'], 'g:i A')) ?></td>
|
||||
<td><span class="status-pill status-<?= e($order['status']) ?>"><?= e(ucfirst((string) $order['status'])) ?></span></td>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="admin-page-actions">
|
||||
<p>Categories and items appear on the storefront as soon as they are active.</p>
|
||||
<a class="button button-ghost" href="/admin/export/menu.pdf">Download Styled PDF</a>
|
||||
<a class="button button-ghost" href="<?= e(url('/admin/export/menu.pdf')) ?>">Download Styled PDF</a>
|
||||
</div>
|
||||
|
||||
<div class="admin-stack">
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="status-dot <?= $category['active'] ? 'is-on' : '' ?>"><?= $category['active'] ? 'Active' : 'Hidden' ?></span>
|
||||
<span class="edit-label">Edit</span>
|
||||
</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/category/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/category/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int) $category['id'] ?>">
|
||||
<div class="form-grid">
|
||||
@@ -35,7 +35,7 @@
|
||||
</div>
|
||||
<details class="new-record">
|
||||
<summary>+ Add Category</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/category/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/category/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="form-grid">
|
||||
<label>Name<input type="text" name="name" required></label>
|
||||
@@ -65,7 +65,7 @@
|
||||
<span class="status-dot <?= $item['active'] ? 'is-on' : '' ?>"><?= $item['active'] ? 'Active' : 'Hidden' ?></span>
|
||||
<span class="edit-label">Edit</span>
|
||||
</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/item/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/item/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int) $item['id'] ?>">
|
||||
<div class="form-grid form-grid-three">
|
||||
@@ -96,7 +96,7 @@
|
||||
</div>
|
||||
<details class="new-record">
|
||||
<summary>+ Add Menu Item</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/item/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/item/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="form-grid form-grid-three">
|
||||
<label>Name<input type="text" name="name" required></label>
|
||||
@@ -122,4 +122,3 @@
|
||||
</details>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="admin-detail-grid newsletter-grid">
|
||||
<section class="admin-panel">
|
||||
<div class="admin-panel-heading"><div><span class="eyebrow">Send an update</span><h2>New Newsletter</h2></div></div>
|
||||
<form class="form-stack" action="/admin/newsletters/send" method="post">
|
||||
<form class="form-stack" action="<?= e(url('/admin/newsletters/send')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<label>Subject line<input type="text" name="subject" maxlength="140" required placeholder="This weekend at Fat Bottom Grille"></label>
|
||||
<label>Message<textarea name="content" rows="12" required placeholder="Share a new special, schedule update, or note from the kitchen..."></textarea></label>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="detail-heading">
|
||||
<div>
|
||||
<a class="back-link" href="/admin/orders">← Back to orders</a>
|
||||
<a class="back-link" href="<?= e(url('/admin/orders')) ?>">← Back to orders</a>
|
||||
<div class="detail-title-row">
|
||||
<h2><?= e($order['order_number']) ?></h2>
|
||||
<span class="status-pill status-<?= e($order['status']) ?>"><?= e(ucfirst((string) $order['status'])) ?></span>
|
||||
@@ -51,7 +51,7 @@
|
||||
<aside>
|
||||
<section class="admin-panel">
|
||||
<div class="admin-panel-heading"><h2>Update Order</h2></div>
|
||||
<form class="form-stack" action="/admin/order" method="post">
|
||||
<form class="form-stack" action="<?= e(url('/admin/order')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="order_id" value="<?= (int) $order['id'] ?>">
|
||||
<label>Status
|
||||
@@ -78,7 +78,7 @@
|
||||
</section>
|
||||
<section class="admin-panel">
|
||||
<div class="admin-panel-heading"><h2>Issue Refund</h2></div>
|
||||
<form class="form-stack" action="/admin/order/refund" method="post">
|
||||
<form class="form-stack" action="<?= e(url('/admin/order/refund')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="order_id" value="<?= (int) $order['id'] ?>">
|
||||
<label>Amount
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="admin-panel">
|
||||
<div class="admin-panel-heading admin-toolbar">
|
||||
<form class="admin-filters" action="/admin/orders" method="get">
|
||||
<form class="admin-filters" action="<?= e(url('/admin/orders')) ?>" method="get">
|
||||
<input type="search" name="q" value="<?= e($query) ?>" placeholder="Order, customer, or email">
|
||||
<select name="status">
|
||||
<option value="">All statuses</option>
|
||||
@@ -10,7 +10,7 @@
|
||||
</select>
|
||||
<button class="button button-small" type="submit">Filter</button>
|
||||
</form>
|
||||
<a class="button button-small button-ghost" href="/admin/export/orders.csv">Export CSV</a>
|
||||
<a class="button button-small button-ghost" href="<?= e(url('/admin/export/orders.csv')) ?>">Export CSV</a>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
@@ -30,7 +30,7 @@
|
||||
<?php if ((int) $order['refunded_cents'] > 0): ?><small><?= money($order['refunded_cents']) ?> returned</small><?php endif; ?>
|
||||
</td>
|
||||
<td><?= money($order['total_cents']) ?></td>
|
||||
<td><a class="button button-small button-ghost" href="/admin/order?id=<?= (int) $order['id'] ?>">Open</a></td>
|
||||
<td><a class="button button-small button-ghost" href="<?= e(url('/admin/order')) ?>?id=<?= (int) $order['id'] ?>">Open</a></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<form class="admin-stack" action="/admin/settings" method="post">
|
||||
<form class="admin-stack" action="<?= e(url('/admin/settings')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<section class="admin-panel">
|
||||
<div class="admin-panel-heading"><div><span class="eyebrow">Public identity</span><h2>Business Information</h2></div></div>
|
||||
@@ -70,7 +70,7 @@
|
||||
<span class="status-dot <?= $tax['active'] ? 'is-on' : '' ?>"><?= $tax['active'] ? 'Active' : 'Disabled' ?></span>
|
||||
<span class="edit-label">Edit</span>
|
||||
</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/tax/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/tax/save')) ?>" method="post">
|
||||
<?= csrf_field() ?><input type="hidden" name="id" value="<?= (int) $tax['id'] ?>">
|
||||
<div class="form-grid">
|
||||
<label>Name<input type="text" name="name" value="<?= e($tax['name']) ?>" required></label>
|
||||
@@ -85,7 +85,7 @@
|
||||
</div>
|
||||
<details class="new-record">
|
||||
<summary>+ Add Tax Rate</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/tax/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/tax/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="form-grid">
|
||||
<label>Name<input type="text" name="name" required></label>
|
||||
@@ -101,7 +101,7 @@
|
||||
<section class="admin-panel settings-followup">
|
||||
<div class="admin-panel-heading"><div><span class="eyebrow">Optional database mode</span><h2>MySQL / MariaDB Migration</h2></div><span class="role-badge"><?= e(strtoupper((string) $config->get('database.driver', 'sqlite'))) ?> active</span></div>
|
||||
<p class="muted">SQLite remains the default and requires no database server. This tool copies every application table and row into MySQL or MariaDB; switching drivers is optional.</p>
|
||||
<form class="form-stack" action="/admin/migrate/mysql" method="post">
|
||||
<form class="form-stack" action="<?= e(url('/admin/migrate/mysql')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="form-grid form-grid-three">
|
||||
<label>Host<input type="text" value="<?= e($config->get('database.mysql_host')) ?>" disabled></label>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<p><?= e($special['description']) ?></p>
|
||||
<div><strong><?= $special['price_cents'] !== null ? money($special['price_cents']) : 'No override' ?></strong><span class="status-dot <?= $special['active'] ? 'is-on' : '' ?>"><?= $special['active'] ? 'Active' : 'Hidden' ?></span></div>
|
||||
</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/special/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/special/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int) $special['id'] ?>">
|
||||
<div class="form-grid">
|
||||
@@ -42,7 +42,7 @@
|
||||
</div>
|
||||
<details class="new-record">
|
||||
<summary>+ Add Homepage Special</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/special/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/special/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="form-grid">
|
||||
<label>Title<input type="text" name="title" required></label>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div class="admin-page-actions">
|
||||
<form class="admin-filters" action="/admin/users" method="get">
|
||||
<form class="admin-filters" action="<?= e(url('/admin/users')) ?>" method="get">
|
||||
<input type="search" name="q" value="<?= e($query) ?>" placeholder="Search name, email, or phone">
|
||||
<button class="button button-small" type="submit">Search</button>
|
||||
</form>
|
||||
@@ -20,7 +20,7 @@
|
||||
<span class="status-dot <?= $user['active'] ? 'is-on' : '' ?>"><?= $user['active'] ? 'Active' : 'Disabled' ?></span>
|
||||
<span class="edit-label">Edit</span>
|
||||
</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/user/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/user/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="id" value="<?= (int) $user['id'] ?>">
|
||||
<div class="form-grid">
|
||||
@@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<details class="new-record">
|
||||
<summary>+ Add Staff Member</summary>
|
||||
<form class="inline-editor form-stack" action="/admin/user/save" method="post">
|
||||
<form class="inline-editor form-stack" action="<?= e(url('/admin/user/save')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="form-grid form-grid-three">
|
||||
<label>Full name<input type="text" name="full_name" required></label>
|
||||
@@ -62,4 +62,3 @@
|
||||
</form>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<span>Your staff account uses the same secure sign-in.</span>
|
||||
</div>
|
||||
</div>
|
||||
<form class="auth-card form-stack" action="/login" method="post">
|
||||
<form class="auth-card form-stack" action="<?= e(url('/login')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<span class="eyebrow">Account access</span>
|
||||
@@ -29,8 +29,7 @@
|
||||
</label>
|
||||
<button class="button button-large button-block" type="submit">Continue Securely</button>
|
||||
<p class="form-footnote">We will email a six-digit verification code after your password is accepted.</p>
|
||||
<p class="auth-switch">New here? <a href="/register">Create an account</a></p>
|
||||
<p class="auth-switch">New here? <a href="<?= e(url('/register')) ?>">Create an account</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<li>Your payment details are handled by Square, not stored here</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form class="auth-card form-stack" action="/register" method="post">
|
||||
<form class="auth-card form-stack" action="<?= e(url('/register')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div>
|
||||
<span class="eyebrow">Customer account</span>
|
||||
@@ -55,7 +55,7 @@
|
||||
</label>
|
||||
<div class="notice">Creating an account opts you into occasional Fat Bottom Grille news. You can opt out from any email or My Account.</div>
|
||||
<button class="button button-large button-block" type="submit">Create My Account</button>
|
||||
<p class="auth-switch">Already have an account? <a href="/login">Sign in</a></p>
|
||||
<p class="auth-switch">Already have an account? <a href="<?= e(url('/login')) ?>">Sign in</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<section class="auth-section section">
|
||||
<div class="container narrow-container">
|
||||
<form class="auth-card form-stack verify-card" action="/verify" method="post">
|
||||
<form class="auth-card form-stack verify-card" action="<?= e(url('/verify')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="verification-icon">6</div>
|
||||
<div>
|
||||
@@ -12,8 +12,7 @@
|
||||
<input class="code-input" type="text" name="code" required inputmode="numeric" maxlength="6" pattern="[0-9]{6}" autocomplete="one-time-code" autofocus>
|
||||
</label>
|
||||
<button class="button button-large button-block" type="submit">Verify & Sign In</button>
|
||||
<a class="text-link text-center" href="/login">Start over</a>
|
||||
<a class="text-link text-center" href="<?= e(url('/login')) ?>">Start over</a>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ $isAdmin = isset($adminPage);
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@600;700;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/assets/app.css">
|
||||
<link rel="stylesheet" href="<?= e(asset('assets/app.css')) ?>">
|
||||
<?php if (!empty($squareEnabled) && !empty($squareAppId) && !empty($squareLocationId)): ?>
|
||||
<script src="<?= $squareEnvironment === 'production'
|
||||
? 'https://web.squarecdn.com/v1/square.js'
|
||||
@@ -36,8 +36,8 @@ $isAdmin = isset($adminPage);
|
||||
<h1><?= e($title ?? 'Dashboard') ?></h1>
|
||||
</div>
|
||||
<div class="admin-top-actions">
|
||||
<a class="button button-ghost button-small" href="/" target="_blank" rel="noopener">View Store</a>
|
||||
<form action="/logout" method="post">
|
||||
<a class="button button-ghost button-small" href="<?= e(url('/')) ?>" target="_blank" rel="noopener">View Store</a>
|
||||
<form action="<?= e(url('/logout')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<button class="button button-small" type="submit">Sign Out</button>
|
||||
</form>
|
||||
@@ -56,8 +56,10 @@ $isAdmin = isset($adminPage);
|
||||
</div>
|
||||
<header class="site-header">
|
||||
<div class="container nav-wrap">
|
||||
<a class="brand" href="/" aria-label="<?= e($businessName) ?> home">
|
||||
<span class="brand-mark">FB</span>
|
||||
<a class="brand" href="<?= e(url('/')) ?>" aria-label="<?= e($businessName) ?> home">
|
||||
<span class="brand-mark">
|
||||
<img src="<?= e(asset('assets/images/fat-bottom-grille-logo.jpg')) ?>" alt="">
|
||||
</span>
|
||||
<span class="brand-copy">
|
||||
<strong><?= e($businessName) ?></strong>
|
||||
<small>Keyser, West Virginia</small>
|
||||
@@ -68,17 +70,17 @@ $isAdmin = isset($adminPage);
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
</button>
|
||||
<nav class="site-nav" id="site-nav" aria-label="Main navigation">
|
||||
<a class="<?= active_path('/') ?>" href="/">Home</a>
|
||||
<a class="<?= active_path('/menu') ?>" href="/menu">Order Online</a>
|
||||
<a class="<?= active_path('/') ?>" href="<?= e(url('/')) ?>">Home</a>
|
||||
<a class="<?= active_path('/menu') ?>" href="<?= e(url('/menu')) ?>">Order Online</a>
|
||||
<?php if ($currentUser): ?>
|
||||
<?php if (in_array($currentUser['role'], ['admin', 'manager', 'staff'], true)): ?>
|
||||
<a href="/admin">Dashboard</a>
|
||||
<a href="<?= e(url('/admin')) ?>">Dashboard</a>
|
||||
<?php endif; ?>
|
||||
<a class="<?= active_path('/account') ?>" href="/account">My Account</a>
|
||||
<a class="<?= active_path('/account') ?>" href="<?= e(url('/account')) ?>">My Account</a>
|
||||
<?php else: ?>
|
||||
<a class="<?= active_path('/login') ?>" href="/login">Sign In</a>
|
||||
<a class="<?= active_path('/login') ?>" href="<?= e(url('/login')) ?>">Sign In</a>
|
||||
<?php endif; ?>
|
||||
<a class="cart-link" href="/cart">
|
||||
<a class="cart-link" href="<?= e(url('/cart')) ?>">
|
||||
Your Order
|
||||
<span class="cart-count"><?= (int) ($currentCart['item_count'] ?? 0) ?></span>
|
||||
</a>
|
||||
@@ -129,6 +131,6 @@ $isAdmin = isset($adminPage);
|
||||
</footer>
|
||||
<?php endif; ?>
|
||||
|
||||
<script src="/assets/app.js" defer></script>
|
||||
<script src="<?= e(asset('assets/app.js')) ?>" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<aside class="admin-sidebar">
|
||||
<a class="admin-brand" href="/admin">
|
||||
<span class="brand-mark">FB</span>
|
||||
<a class="admin-brand" href="<?= e(url('/admin')) ?>">
|
||||
<span class="brand-mark">
|
||||
<img src="<?= e(asset('assets/images/fat-bottom-grille-logo.jpg')) ?>" alt="">
|
||||
</span>
|
||||
<span>
|
||||
<strong><?= e($config->get('business.name')) ?></strong>
|
||||
<small>Operations</small>
|
||||
</span>
|
||||
</a>
|
||||
<nav class="admin-nav" aria-label="Dashboard navigation">
|
||||
<a class="<?= $adminPage === 'dashboard' ? 'is-active' : '' ?>" href="/admin">Overview</a>
|
||||
<a class="<?= $adminPage === 'orders' ? 'is-active' : '' ?>" href="/admin/orders">Orders</a>
|
||||
<a class="<?= $adminPage === 'menu' ? 'is-active' : '' ?>" href="/admin/menu">Menu</a>
|
||||
<a class="<?= $adminPage === 'specials' ? 'is-active' : '' ?>" href="/admin/specials">Specials</a>
|
||||
<a class="<?= $adminPage === 'newsletters' ? 'is-active' : '' ?>" href="/admin/newsletters">Newsletters</a>
|
||||
<a class="<?= $adminPage === 'dashboard' ? 'is-active' : '' ?>" href="<?= e(url('/admin')) ?>">Overview</a>
|
||||
<a class="<?= $adminPage === 'orders' ? 'is-active' : '' ?>" href="<?= e(url('/admin/orders')) ?>">Orders</a>
|
||||
<a class="<?= $adminPage === 'menu' ? 'is-active' : '' ?>" href="<?= e(url('/admin/menu')) ?>">Menu</a>
|
||||
<a class="<?= $adminPage === 'specials' ? 'is-active' : '' ?>" href="<?= e(url('/admin/specials')) ?>">Specials</a>
|
||||
<a class="<?= $adminPage === 'newsletters' ? 'is-active' : '' ?>" href="<?= e(url('/admin/newsletters')) ?>">Newsletters</a>
|
||||
<?php if (($currentUser['role'] ?? '') === 'admin'): ?>
|
||||
<a class="<?= $adminPage === 'users' ? 'is-active' : '' ?>" href="/admin/users">Users & Staff</a>
|
||||
<a class="<?= $adminPage === 'settings' ? 'is-active' : '' ?>" href="/admin/settings">Settings</a>
|
||||
<a class="<?= $adminPage === 'users' ? 'is-active' : '' ?>" href="<?= e(url('/admin/users')) ?>">Users & Staff</a>
|
||||
<a class="<?= $adminPage === 'settings' ? 'is-active' : '' ?>" href="<?= e(url('/admin/settings')) ?>">Settings</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<div class="admin-user">
|
||||
@@ -25,4 +27,3 @@
|
||||
</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ $tags = is_array($tags) ? $tags : [];
|
||||
<?php foreach ($tags as $tag): ?><span><?= e($tag) ?></span><?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<form class="quick-add" action="/cart/add" method="post">
|
||||
<form class="quick-add" action="<?= e(url('/cart/add')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="menu_item_id" value="<?= (int) $item['id'] ?>">
|
||||
<input type="hidden" name="return_to" value="<?= e($_SERVER['REQUEST_URI'] ?? '/menu') ?>">
|
||||
<input type="hidden" name="return_to" value="<?= e(\FatBottom\Core\Http::path()) ?>">
|
||||
<label>
|
||||
<span class="sr-only">Quantity</span>
|
||||
<select name="quantity" aria-label="Quantity for <?= e($item['name']) ?>">
|
||||
@@ -37,4 +37,3 @@ $tags = is_array($tags) ? $tags : [];
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
<div class="empty-state">
|
||||
<h2>Your order is wide open.</h2>
|
||||
<p>Start with a burger, a burrito, or whatever sounds good right now.</p>
|
||||
<a class="button" href="/menu">Browse the Menu</a>
|
||||
<a class="button" href="<?= e(url('/menu')) ?>">Browse the Menu</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<form action="/cart/update" method="post">
|
||||
<form action="<?= e(url('/cart/update')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<div class="cart-list">
|
||||
<?php foreach ($cart['items'] as $item): ?>
|
||||
@@ -30,12 +30,12 @@
|
||||
<input type="number" min="0" max="20" name="quantity[<?= (int) $item['id'] ?>]" value="<?= (int) $item['quantity'] ?>">
|
||||
</label>
|
||||
<strong class="cart-line-total"><?= money((int) $item['quantity'] * (int) $item['unit_price_cents']) ?></strong>
|
||||
<button class="icon-button remove-cart-item" type="submit" formaction="/cart/remove" name="cart_item_id" value="<?= (int) $item['id'] ?>" aria-label="Remove <?= e($item['name']) ?>">×</button>
|
||||
<button class="icon-button remove-cart-item" type="submit" formaction="<?= e(url('/cart/remove')) ?>" name="cart_item_id" value="<?= (int) $item['id'] ?>" aria-label="Remove <?= e($item['name']) ?>">×</button>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="cart-actions">
|
||||
<a class="text-link" href="/menu">Add More Items</a>
|
||||
<a class="text-link" href="<?= e(url('/menu')) ?>">Add More Items</a>
|
||||
<button class="button button-outline" type="submit">Update Order</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -50,9 +50,8 @@
|
||||
<div><dt>Estimated tax</dt><dd><?= money($totals['tax_cents']) ?></dd></div>
|
||||
<div class="summary-total"><dt>Total</dt><dd><?= money($totals['total_cents']) ?></dd></div>
|
||||
</dl>
|
||||
<a class="button button-block button-large" href="/checkout">Continue to Checkout</a>
|
||||
<a class="button button-block button-large" href="<?= e(url('/checkout')) ?>">Continue to Checkout</a>
|
||||
<p class="summary-note">Pickup at 410 W Piedmont St, Keyser.</p>
|
||||
</aside>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="container checkout-layout section">
|
||||
<section class="checkout-main">
|
||||
<form id="checkout-form" class="panel form-stack"
|
||||
action="/checkout" method="post"
|
||||
action="<?= e(url('/checkout')) ?>" method="post"
|
||||
data-square-enabled="<?= $squareEnabled ? 'true' : 'false' ?>"
|
||||
data-square-app-id="<?= e($squareAppId) ?>"
|
||||
data-square-location-id="<?= e($squareLocationId) ?>">
|
||||
@@ -95,4 +95,3 @@
|
||||
<p class="summary-note">Estimated ready time: <?= (int) $config->get('ordering.pickup_eta_minutes', 25) ?> minutes.</p>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<h1>Big flavor.<br><em>No shortcuts.</em></h1>
|
||||
<p>Stacked burgers, loaded burritos, and dinner plates that eat like Sunday supper. Order ahead and we will have it hot.</p>
|
||||
<div class="hero-actions">
|
||||
<a class="button button-large" href="/menu">Start Your Order</a>
|
||||
<a class="button button-large" href="<?= e(url('/menu')) ?>">Start Your Order</a>
|
||||
<?php if ($config->get('business.phone')): ?>
|
||||
<a class="text-link" href="tel:<?= e(preg_replace('/[^0-9+]/', '', (string) $config->get('business.phone'))) ?>">
|
||||
Call <?= e($config->get('business.phone')) ?>
|
||||
@@ -18,19 +18,15 @@
|
||||
<span><?= (bool) $config->get('ordering.accepting_orders', true) ? 'Ordering now open' : 'Ordering currently paused' ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-plate" aria-hidden="true">
|
||||
<div class="plate-ring">
|
||||
<div class="burger">
|
||||
<span class="bun bun-top"></span>
|
||||
<span class="lettuce"></span>
|
||||
<span class="cheese"></span>
|
||||
<span class="patty"></span>
|
||||
<span class="onion"></span>
|
||||
<span class="patty patty-two"></span>
|
||||
<span class="bun bun-bottom"></span>
|
||||
<div class="hero-plate">
|
||||
<div class="hero-food-photo">
|
||||
<img src="<?= e(asset('assets/images/chicken-sandwich-fries.jpg')) ?>" alt="Chicken sandwich and hand-cut fries from Fat Bottom Grille">
|
||||
<div class="hero-photo-caption">
|
||||
<span>Made in Keyser</span>
|
||||
<strong>Hot. Fresh. Generous.</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-stamp">
|
||||
<div class="hero-stamp" aria-hidden="true">
|
||||
<strong>410</strong>
|
||||
<span>W Piedmont</span>
|
||||
</div>
|
||||
@@ -46,7 +42,7 @@
|
||||
<span class="eyebrow">Happening now</span>
|
||||
<h2>Today’s Specials</h2>
|
||||
</div>
|
||||
<a class="text-link" href="/menu">See the full menu</a>
|
||||
<a class="text-link" href="<?= e(url('/menu')) ?>">See the full menu</a>
|
||||
</div>
|
||||
<div class="special-grid special-count-<?= min(count($specials), 4) ?>">
|
||||
<?php foreach ($specials as $special): ?>
|
||||
@@ -59,7 +55,7 @@
|
||||
<strong><?= money($special['price_cents']) ?></strong>
|
||||
<?php endif; ?>
|
||||
<?php if ($special['menu_item_id']): ?>
|
||||
<form action="/cart/add" method="post">
|
||||
<form action="<?= e(url('/cart/add')) ?>" method="post">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="menu_item_id" value="<?= (int) $special['menu_item_id'] ?>">
|
||||
<input type="hidden" name="quantity" value="1">
|
||||
@@ -97,7 +93,7 @@
|
||||
<span class="eyebrow">Pick your direction</span>
|
||||
<div class="category-links">
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<a href="/menu#<?= e($category['slug']) ?>">
|
||||
<a href="<?= e(url('/menu')) ?>#<?= e($category['slug']) ?>">
|
||||
<strong><?= e($category['name']) ?></strong>
|
||||
<span><?= count($category['items']) ?> picks</span>
|
||||
</a>
|
||||
@@ -116,7 +112,7 @@
|
||||
<span class="eyebrow">Proudly from Keyser</span>
|
||||
<h2>Food that shows up hungry.</h2>
|
||||
<p>Fat Bottom Grille is the kind of place where the portions are honest and nobody leaves wondering what is for dinner. Find us at 410 W Piedmont Street, right here in Keyser.</p>
|
||||
<a class="button button-outline" href="/menu">Order for Pickup</a>
|
||||
<a class="button button-outline" href="<?= e(url('/menu')) ?>">Order for Pickup</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<h1>Order Online</h1>
|
||||
<p>Choose your favorites and we will get the kitchen moving.</p>
|
||||
</div>
|
||||
<form class="menu-search" action="/menu" method="get">
|
||||
<form class="menu-search" action="<?= e(url('/menu')) ?>" method="get">
|
||||
<label for="menu-query">Search the menu</label>
|
||||
<div class="search-row">
|
||||
<input id="menu-query" type="search" name="q" value="<?= e($query) ?>" placeholder="Burger, chicken, fries...">
|
||||
@@ -29,7 +29,7 @@
|
||||
<span class="eyebrow">No match yet</span>
|
||||
<h2>We could not find “<?= e($query) ?>”</h2>
|
||||
<p>Try another search or browse the full menu.</p>
|
||||
<a class="button" href="/menu">Clear Search</a>
|
||||
<a class="button" href="<?= e(url('/menu')) ?>">Clear Search</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($categories as $category): ?>
|
||||
@@ -49,4 +49,3 @@
|
||||
</section>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="confirmation-actions">
|
||||
<a class="button" href="/menu">Order Something Else</a>
|
||||
<?php if ($currentUser): ?><a class="button button-outline" href="/account">View My Orders</a><?php endif; ?>
|
||||
<a class="button" href="<?= e(url('/menu')) ?>">Order Something Else</a>
|
||||
<?php if ($currentUser): ?><a class="button button-outline" href="<?= e(url('/account')) ?>">View My Orders</a><?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||