From 193ff815a19df5f8f2c3d4ad55d3c1f4e419de80 Mon Sep 17 00:00:00 2001 From: qdust41 Date: Sat, 4 Apr 2026 12:38:40 -0400 Subject: [PATCH] Mobile nav and drafting setup --- assets/css/app.css | 157 ++++++++++++++++++++++++++++++++++++++++++++ assets/js/index.tsx | 103 +++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+) diff --git a/assets/css/app.css b/assets/css/app.css index 672a92d..e599027 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -159,6 +159,22 @@ html, body { @media (max-width: 640px) { .mx-root { grid-template-columns: 1fr; } .mx-sidebar { display: none; } + /* space for fixed bottom nav */ + .mx-main { padding-bottom: 72px; } + /* hide inline compose on mobile — the overlay handles it */ + .mx-compose-wrapper { display: none; } + /* tighten card spacing */ + .mx-feed { padding: 0.625rem 0.625rem; gap: 0.5rem; } + .mx-tweet { padding: 0.875rem 0.875rem; } + /* truncate long email addresses */ + .mx-tweet-handle { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 180px; + } + .mx-header { padding: 0.75rem 1rem; } + .mx-detail { padding: 0.875rem 1rem; } } /* ── Sidebar ── */ @@ -802,3 +818,144 @@ html, body { background: var(--mx-border); margin: 4px 0; } + +/* ───────────────────────────────────────────────────────────────────────────── + Mobile bottom navigation bar + Only shown at ≤640 px (sidebar is hidden at that breakpoint). +───────────────────────────────────────────────────────────────────────────── */ +.mx-mobile-nav { + display: none; + position: fixed; + bottom: 0; + left: 0; + right: 0; + z-index: 50; + height: 60px; + align-items: center; + justify-content: space-around; + background: color-mix(in oklch, var(--mx-bg) 92%, transparent); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border-top: 1px solid var(--mx-border); + /* respect iPhone home indicator */ + padding-bottom: env(safe-area-inset-bottom, 0px); +} + +@media (max-width: 640px) { + .mx-mobile-nav { display: flex; } +} + +.mx-mobile-nav-item { + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + color: var(--mx-muted); + text-decoration: none; + font-size: 0.65rem; + font-weight: 500; + padding: 0.25rem 0; + transition: color 0.15s; + -webkit-tap-highlight-color: transparent; +} + +.mx-mobile-nav-item--active { color: var(--mx-accent); } + +.mx-mobile-nav-item svg { + flex-shrink: 0; +} + +/* Centred compose button — raised pill */ +.mx-mobile-nav-compose { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--mx-accent); + color: #fff; + border: none; + cursor: pointer; + box-shadow: 0 4px 16px color-mix(in oklch, var(--mx-accent) 55%, transparent); + transition: background 0.15s, transform 0.12s, box-shadow 0.15s; + -webkit-tap-highlight-color: transparent; +} + +.mx-mobile-nav-compose:hover { + background: var(--mx-accent2); + box-shadow: 0 6px 20px color-mix(in oklch, var(--mx-accent) 65%, transparent); + transform: scale(1.06); +} + +.mx-mobile-nav-compose:active { transform: scale(0.94); } + +/* ───────────────────────────────────────────────────────────────────────────── + Mobile compose overlay (full-screen drafting page) + Hidden on desktop — only the mobile nav can trigger it. +───────────────────────────────────────────────────────────────────────────── */ +@keyframes mx-overlay-in { + from { opacity: 0; transform: translateY(28px); } + to { opacity: 1; transform: translateY(0); } +} + +.mx-compose-overlay { + display: none; +} + +@media (max-width: 640px) { + .mx-compose-overlay { + display: flex; + flex-direction: column; + position: fixed; + inset: 0; + z-index: 100; + background: var(--mx-bg); + animation: mx-overlay-in 0.22s cubic-bezier(0.34, 1.1, 0.64, 1); + } +} + +.mx-compose-overlay-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--mx-border); + background: color-mix(in oklch, var(--mx-bg) 85%, transparent); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + position: sticky; + top: 0; + z-index: 1; +} + +.mx-compose-overlay-title { + font-family: 'Instrument Serif', Georgia, serif; + font-size: 1.125rem; + font-style: italic; + letter-spacing: -0.01em; + color: var(--mx-fg); +} + +.mx-compose-overlay-cancel { + background: none; + border: none; + color: var(--mx-fg2); + font-size: 0.9rem; + font-family: inherit; + cursor: pointer; + padding: 0.25rem 0; + min-width: 60px; + transition: color 0.15s; +} + +.mx-compose-overlay-cancel:hover { color: var(--mx-fg); } + +.mx-compose-overlay-body { + flex: 1; + overflow-y: auto; + padding: 1.25rem; +} diff --git a/assets/js/index.tsx b/assets/js/index.tsx index 088485e..ee72b4e 100644 --- a/assets/js/index.tsx +++ b/assets/js/index.tsx @@ -1104,6 +1104,96 @@ function UserDetail({ userId }: { userId: string }) { ); } +// ── Mobile bottom nav ───────────────────────────────────────────────────────── + +function MobileNav({ + page, + onCompose, +}: { + page: string; + onCompose: () => void; +}) { + const onFeedPage = page === "feed" || page === "tweet"; + const onUsersPage = page === "users" || page === "user-detail"; + + return ( + + ); +} + +// ── Mobile compose overlay ───────────────────────────────────────────────────── + +function MobileComposePage({ + email, + onClose, +}: { + email: string; + onClose: () => void; +}) { + return ( +
+
+ + New Post + {/* right spacer keeps title centred */} +
+
+
+ {email ? ( + + ) : ( +
+

Sign in to start mixing.

+ Sign in +
+ )} +
+
+ ); +} + function App() { const appEl = document.getElementById("app")!; const email = appEl.dataset.currentUserEmail ?? ""; @@ -1112,6 +1202,8 @@ function App() { const page = appEl.dataset.page ?? "feed"; const profileUserId = appEl.dataset.userId || null; + const [mobileCompose, setMobileCompose] = useState(false); + const onFeedPage = page === "feed" || page === "tweet"; const onUsersPage = page === "users" || page === "user-detail"; @@ -1228,6 +1320,17 @@ function App() {
+ + {/* Mobile-only bottom nav — hidden on desktop via CSS */} + setMobileCompose(true)} /> + + {/* Mobile compose overlay — only visible on mobile via CSS */} + {mobileCompose && ( + setMobileCompose(false)} + /> + )} );