From 0574e580fb2ee2ca6bd3258a3f460d6b0da13ff6 Mon Sep 17 00:00:00 2001 From: Zeeshaun Date: Tue, 5 May 2026 19:46:42 +0000 Subject: [PATCH] Hide admin tabs behind logo long press --- client/src/App.jsx | 82 ++++++++++++++++++++++++---- client/src/styles.css | 121 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 185 insertions(+), 18 deletions(-) diff --git a/client/src/App.jsx b/client/src/App.jsx index cb5985f..458076d 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { createMenuItem, createOrder, @@ -8,16 +8,17 @@ import { resetMenu, updateOrderStatus } from "./api.js"; -import logo from "../../assets/ranaze-logo.png"; +import logo from "../../assets/ranaze-logo-transparent.png"; +import heroCup from "../../assets/ranaze-bubble-tea-single-cup-transparent.png"; const queueStatuses = ["new", "making", "ready"]; const orderIdleTimeoutMs = 90_000; +const adminUnlockPressMs = 3_000; const tabLabels = { order: "Order", queue: "Order Status", menu: "Menu Admin" }; - const initialBuilder = (settings) => ({ size: settings.sizeOptions[0]?.name ?? "Regular", sweetness: settings.sweetnessOptions[3] ?? settings.sweetnessOptions[0] ?? "100%", @@ -27,8 +28,11 @@ const initialBuilder = (settings) => ({ }); export default function App() { + const adminPressTimerRef = useRef(null); const [view, setView] = useState("order"); const [orderMode, setOrderMode] = useState(null); + const [adminUnlocked, setAdminUnlocked] = useState(false); + const [logoPressing, setLogoPressing] = useState(false); const [menu, setMenu] = useState([]); const [orders, setOrders] = useState([]); const [settings, setSettings] = useState(null); @@ -51,6 +55,16 @@ export default function App() { loadBootstrap(); }, []); + useEffect(() => { + return () => window.clearTimeout(adminPressTimerRef.current); + }, []); + + useEffect(() => { + if (!adminUnlocked && view !== "order") { + setView("order"); + } + }, [adminUnlocked, view]); + useEffect(() => { if (view !== "order" || !orderMode) { return; @@ -135,6 +149,7 @@ export default function App() { return selectedCategory === "All" || item.category === selectedCategory; }); const selectedDrink = menu.find((item) => item._id === selectedDrinkId) || null; + const visibleTabs = adminUnlocked ? ["order", "queue", "menu"] : ["order"]; const modeDetails = { premade: { stepLabel: "House favorite", @@ -157,13 +172,35 @@ export default function App() { }; const activeMode = orderMode ? modeDetails[orderMode] : null; + function startAdminPress() { + window.clearTimeout(adminPressTimerRef.current); + setLogoPressing(true); + adminPressTimerRef.current = window.setTimeout(() => { + setAdminUnlocked((current) => !current); + setLogoPressing(false); + }, adminUnlockPressMs); + } + + function cancelAdminPress() { + window.clearTimeout(adminPressTimerRef.current); + setLogoPressing(false); + } + return (
-
+
+

Ranaze Bubble Tea & Slush

Choose your drink

@@ -173,7 +210,7 @@ export default function App() {
@@ -207,6 +249,13 @@ export default function App() { Start with a signature favorite, make a few quick changes, or fully customize your drink.

+
+
+

Freshly made for you

+

Bubble tea, fruit tea, and slushes with easy customization.

+
+ Ranaze signature bubble tea +
))} @@ -675,6 +729,16 @@ function SummaryCard({ label, value }) { ); } +function categoryMonogram(category) { + const map = { + "Milk Tea": "MT", + "Fruit Tea": "FT", + Specialty: "SP" + }; + + return map[category] || category.slice(0, 2).toUpperCase(); +} + function nextStatusButton(order, onAdvance) { const map = { new: "making", diff --git a/client/src/styles.css b/client/src/styles.css index d567e9e..7aaca24 100644 --- a/client/src/styles.css +++ b/client/src/styles.css @@ -14,6 +14,20 @@ font-family: "Avenir Next", "Trebuchet MS", sans-serif; } +body::before { + content: ""; + position: fixed; + inset: 0; + background: + linear-gradient(180deg, rgba(255, 249, 239, 0.88), rgba(245, 231, 208, 0.94)), + radial-gradient(circle at 10% 18%, rgba(246, 179, 62, 0.15), transparent 18%), + radial-gradient(circle at 88% 16%, rgba(244, 221, 174, 0.22), transparent 20%), + radial-gradient(circle at 16% 82%, rgba(244, 221, 174, 0.16), transparent 18%), + radial-gradient(circle at 84% 86%, rgba(246, 179, 62, 0.12), transparent 18%); + pointer-events: none; + z-index: -1; +} + * { box-sizing: border-box; } @@ -72,22 +86,36 @@ button { } .brand-mark { - width: 104px; - height: 104px; + width: 120px; + height: 120px; flex: 0 0 auto; - border-radius: 28px; - background: rgba(255, 255, 255, 0.88); - border: 1px solid rgba(16, 16, 16, 0.06); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.7); + border-radius: 32px; + background: radial-gradient(circle at center, rgba(255, 255, 255, 0.9), rgba(255, 248, 232, 0.72)); + border: 1px solid rgba(16, 16, 16, 0.04); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.7), + 0 16px 32px rgba(83, 61, 24, 0.1); display: grid; place-items: center; overflow: hidden; + padding: 0; + appearance: none; + cursor: default; + transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease; +} + +.brand-mark.is-pressing { + transform: scale(0.98); + border-color: rgba(246, 179, 62, 0.45); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.7), + 0 10px 20px rgba(83, 61, 24, 0.14); } .brand-mark img { - width: 100%; - height: 100%; - object-fit: cover; + width: 88%; + height: 88%; + object-fit: contain; } .brand-copy { @@ -198,6 +226,36 @@ button { gap: 1rem; } +.mode-hero { + display: grid; + grid-template-columns: 1fr 220px; + gap: 1rem; + align-items: center; + padding: 1.1rem 1.25rem; + border-radius: 26px; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.76), rgba(247, 221, 161, 0.36)); + border: 1px solid rgba(16, 16, 16, 0.06); +} + +.mode-hero-copy { + display: grid; + gap: 0.4rem; +} + +.mode-hero-copy h3 { + margin: 0; + font-size: clamp(1.2rem, 2vw, 1.8rem); + line-height: 1.05; +} + +.mode-hero-image { + width: 100%; + max-height: 220px; + object-fit: contain; + justify-self: end; + filter: drop-shadow(0 22px 24px rgba(83, 61, 24, 0.16)); +} + .mode-card { display: grid; gap: 0.65rem; @@ -265,6 +323,10 @@ button { box-shadow: 0 8px 18px rgba(16, 16, 16, 0.18); } +.admin-lock-btn { + color: var(--muted); +} + .text-btn { background: transparent; } @@ -309,6 +371,37 @@ button { text-align: left; } +.drink-card { + display: grid; + grid-template-columns: 74px 1fr auto; + align-items: center; + gap: 0.9rem; +} + +.drink-card-badge { + width: 74px; + height: 74px; + border-radius: 20px; + background: linear-gradient(135deg, rgba(255, 255, 255, 0.96), rgba(248, 236, 210, 0.9)); + border: 1px solid rgba(16, 16, 16, 0.06); + display: grid; + place-items: center; + overflow: hidden; +} + +.drink-card-badge span { + font-family: Georgia, "Times New Roman", serif; + font-size: 1.15rem; + font-weight: 700; + letter-spacing: 0.08em; + color: #191512; +} + +.drink-card-copy { + display: grid; + gap: 0.2rem; +} + .drink-card h3, .ticket-line strong, .queue-card h3 { @@ -448,6 +541,7 @@ select { @media (max-width: 980px) { .topbar, .screen, + .mode-hero, .mode-grid, .queue-columns, .menu-item, @@ -466,6 +560,15 @@ select { height: 88px; } + .drink-card { + grid-template-columns: 1fr; + } + + .drink-card-badge { + width: 64px; + height: 64px; + } + .order-toolbar { align-items: flex-start; flex-direction: column;