Responsive Tailwind Redesign — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Replace all raw CSS @media queries with Tailwind responsive utilities and implement mobile-first responsive layouts (single-column mobile, two-column tablet, three-column desktop).
Architecture: Mobile-first approach using Tailwind v4 default breakpoints (sm: 640px, md: 768px, lg: 1024px). The existing three-column desktop layout is preserved at lg+. Below lg, a header bar with slide-down nav panel replaces the left sidebar. Below md, chat is hidden behind a floating button overlay.
Tech Stack: Astro v5, Tailwind CSS v4 (via Vite plugin), TypeScript
Task 1: BaseLayout.css — Remove media queries and update layout classes
Files:
- Modify:
app/src/layouts/BaseLayout/BaseLayout.css:5-255
Step 1: Remove the --spacing-mobile-panel theme token
In BaseLayout.css, delete line 27:
--spacing-mobile-panel: calc(100svh - 48px);Step 2: Replace #layout styles with mobile-first responsive classes
Replace:
#layout { @apply flex w-screen h-svh;}With:
#layout { @apply flex flex-col lg:flex-row w-screen h-svh;}Step 3: Replace #site-nav styles
Replace:
#site-nav { @apply shrink-0 grow-0 basis-1/5 max-w-64 py-3 pl-3 pr-0;}With:
#site-nav { @apply hidden lg:block lg:shrink-0 lg:grow-0 lg:basis-1/5 lg:max-w-64 lg:py-3 lg:pl-3 lg:pr-0;}Step 4: Replace #content-viewer styles
Replace:
#content-viewer { @apply grow shrink basis-0 min-w-0 py-6 px-6;}With:
#content-viewer { @apply grow shrink basis-0 min-w-0 py-4 px-4 sm:py-6 sm:px-6;}Step 5: Replace #chat-panel styles
Replace:
#chat-panel { @apply shrink-0 grow-0 basis-1/4 max-w-80 border-l border-border p-4;}With:
#chat-panel { @apply hidden md:flex md:shrink-0 md:grow-0 md:basis-1/4 md:max-w-80 md:border-l md:border-border md:p-4;}Step 6: Replace #mobile-toolbar styles
Replace:
#mobile-toolbar { @apply hidden;}With:
#mobile-toolbar { @apply flex items-center justify-between fixed top-0 left-0 w-full h-12 px-3 bg-surface border-b border-border z-60 lg:hidden;}
#mobile-toolbar button { @apply bg-transparent border-none p-2 text-muted hover:text-neon;}
#mobile-title { @apply font-display font-bold text-base text-neon;}
#mobile-toolbar-right { @apply flex items-center gap-1;}Step 7: Replace #drawer-backdrop styles
Replace:
#drawer-backdrop { @apply hidden;}With:
#nav-backdrop { @apply hidden fixed top-12 left-0 w-full h-full z-40 bg-midnight/70 backdrop-blur-xs lg:hidden;}
#nav-backdrop.visible { @apply block;}
#chat-overlay-backdrop { @apply hidden fixed inset-0 z-40 bg-midnight/70 backdrop-blur-xs md:hidden;}
#chat-overlay-backdrop.visible { @apply block;}Step 8: Add nav slide-down panel styles
After the backdrop styles, add:
#nav-panel { @apply fixed top-12 left-0 w-full max-h-[60vh] overflow-y-auto bg-midnight/95 backdrop-blur-md border-b border-border z-50 -translate-y-full transition-transform duration-300 ease-in-out lg:hidden;}
#nav-panel.open { @apply translate-y-0;}Step 9: Add mobile chat overlay styles
After the nav panel styles, add:
#chat-overlay { @apply fixed bottom-0 left-0 w-full h-[70vh] bg-midnight border-t border-border z-50 translate-y-full transition-transform duration-300 ease-in-out md:hidden flex flex-col p-4;}
#chat-overlay.open { @apply translate-y-0;}
#chat-overlay-close { @apply self-end bg-transparent border-none p-2 text-muted hover:text-neon mb-2;}Step 10: Add mobile content padding for toolbar
After the overlay styles, add:
#content-viewer { @apply pt-16 lg:pt-4;}Wait — this would conflict with the earlier #content-viewer rule. Instead, merge the pt-16 into the existing rule:
Go back to Step 4 and use this instead:
#content-viewer { @apply grow shrink basis-0 min-w-0 pt-16 pb-4 px-4 sm:px-6 sm:pb-6 lg:pt-6 lg:pb-6;}Step 11: Add floating chat button styles
#mobile-chat-fab { @apply fixed bottom-4 right-4 z-30 w-12 h-12 rounded-full bg-neon text-midnight flex items-center justify-center shadow-lg md:hidden;}
#mobile-chat-fab:hover { @apply bg-teal;}Step 12: Delete the entire @media (max-width: 767px) block
Delete lines 202–255 (the entire @media (max-width: 767px) { ... } block and everything inside it).
Step 13: Run the build to verify no CSS errors
Run: just app::build
Expected: Build succeeds (pages may look wrong visually, but no compile errors)
Step 14: Commit
git add app/src/layouts/BaseLayout/BaseLayout.cssgit commit -m "refactor(layout): replace BaseLayout CSS media queries with Tailwind responsive utilities"Task 2: BaseLayout.astro — Restructure markup for responsive layout
Files:
- Modify:
app/src/layouts/BaseLayout/BaseLayout.astro:36-96
Step 1: Replace the mobile toolbar chat button with md:hidden
In #mobile-toolbar-right, add class="md:hidden" to the chat button:
Replace:
<button id="mobile-chat-btn" type="button" aria-label="Open chat">With:
<button id="mobile-chat-btn" class="md:hidden" type="button" aria-label="Open chat">Step 2: Replace #drawer-backdrop with nav and chat backdrops
Replace:
<div id="drawer-backdrop"></div>With:
<div id="nav-backdrop" class="lg:hidden"></div> <div id="chat-overlay-backdrop" class="md:hidden"></div>Step 3: Add the nav slide-down panel wrapping a duplicate ProjectTree
After the #nav-backdrop div and before <div id="layout">, add:
<div id="nav-panel" class="lg:hidden"> <ProjectTree activePage={activePage} activeCollection={activeCollection} /> </div>Note: This means ProjectTree is rendered twice — once in the sidebar (visible on lg+) and once in the nav panel (visible on < lg). This is the simplest approach since Astro SSG renders both at build time; there’s no runtime cost beyond the HTML.
Step 4: Add floating chat button and chat overlay
After </div> (closing #layout) and before <CommandPalette />, add:
<button id="mobile-chat-fab" type="button" aria-label="Open chat"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path> </svg> </button> <div id="chat-overlay" class="md:hidden"> <button id="chat-overlay-close" type="button" aria-label="Close chat"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> <div id="chat-overlay-content"></div> </div>Step 5: Build to verify
Run: just app::build
Expected: Build succeeds
Step 6: Commit
git add app/src/layouts/BaseLayout/BaseLayout.astrogit commit -m "refactor(layout): restructure BaseLayout markup for responsive nav panel and chat overlay"Task 3: BaseLayout.ts — Rewrite drawer logic
Files:
- Modify:
app/src/layouts/BaseLayout/BaseLayout.ts:1-57
Step 1: Rewrite the entire file
Replace the full contents of BaseLayout.ts with:
function initResponsiveUI() { const menuBtn = document.getElementById("mobile-menu-btn"); const searchBtn = document.getElementById("mobile-search-btn"); const chatBtn = document.getElementById("mobile-chat-btn"); const navPanel = document.getElementById("nav-panel"); const navBackdrop = document.getElementById("nav-backdrop"); const chatFab = document.getElementById("mobile-chat-fab"); const chatOverlay = document.getElementById("chat-overlay"); const chatOverlayClose = document.getElementById("chat-overlay-close"); const chatOverlayBackdrop = document.getElementById("chat-overlay-backdrop");
function closeNav() { navPanel?.classList.remove("open"); navBackdrop?.classList.remove("visible"); document.body.style.overflow = ""; }
function closeChat() { chatOverlay?.classList.remove("open"); chatOverlayBackdrop?.classList.remove("visible"); document.body.style.overflow = ""; }
function closeAll() { closeNav(); closeChat(); }
// Nav slide-down panel toggle menuBtn?.addEventListener("click", () => { const willOpen = !navPanel?.classList.contains("open"); closeAll(); if (willOpen) { navPanel?.classList.add("open"); navBackdrop?.classList.add("visible"); document.body.style.overflow = "hidden"; } });
navBackdrop?.addEventListener("click", closeNav);
// Close nav when a nav link is clicked navPanel?.querySelectorAll("a").forEach((link) => { link.addEventListener("click", closeNav); });
// Mobile chat overlay toggle (toolbar button) chatBtn?.addEventListener("click", () => { closeNav(); chatOverlay?.classList.add("open"); chatOverlayBackdrop?.classList.add("visible"); document.body.style.overflow = "hidden"; });
// Mobile chat FAB chatFab?.addEventListener("click", () => { closeNav(); chatOverlay?.classList.add("open"); chatOverlayBackdrop?.classList.add("visible"); document.body.style.overflow = "hidden"; });
// Close chat overlay chatOverlayClose?.addEventListener("click", closeChat); chatOverlayBackdrop?.addEventListener("click", closeChat);
// Search button closes everything (CommandPalette handles opening itself) searchBtn?.addEventListener("click", closeAll);}
document.addEventListener("astro:page-load", initResponsiveUI);
// Preserve nav scroll across View Transitionslet savedNavScroll = 0;document.addEventListener("astro:before-swap", () => { const nav = document.getElementById("site-nav"); if (nav) savedNavScroll = nav.scrollTop; document.body.style.overflow = "";});document.addEventListener("astro:after-swap", () => { const nav = document.getElementById("site-nav"); if (nav) nav.scrollTop = savedNavScroll;});Step 2: Build to verify
Run: just app::build
Expected: Build succeeds
Step 3: Commit
git add app/src/layouts/BaseLayout/BaseLayout.tsgit commit -m "refactor(layout): rewrite BaseLayout.ts for responsive nav panel and chat overlay"Task 4: ProjectTree — Remove media queries, add responsive classes
Files:
- Modify:
app/src/components/ProjectTree/ProjectTree.css:1-88 - Modify:
app/src/components/ProjectTree/ProjectTree.astro:13-66
Step 1: Remove media queries from ProjectTree.css
Delete lines 8-12 (the @media (min-width: 768px) block):
@media (min-width: 768px) { #site-nav { @apply relative; }}Delete lines 80-88 (the @media (max-width: 767px) block):
@media (max-width: 767px) { .nav-search-trigger { @apply hidden; }
#site-nav h2 { @apply hidden; }}And add relative to the base #site-nav rule (since on lg+ it’s always visible and needs position: relative):
Replace:
#site-nav { @apply font-body;}With:
#site-nav { @apply font-body relative;}Step 2: Add responsive visibility classes in ProjectTree.astro
The search trigger and brand heading are always visible now since #site-nav itself is hidden lg:block. But in the nav panel (mobile/tablet), we also want the heading and search trigger visible. Since both instances of ProjectTree render the same markup, and #site-nav handles visibility via hidden lg:block, the nav panel copy shows everything. This works as-is — no markup changes needed for ProjectTree.astro.
Actually, wait — the search trigger should still be hidden inside the nav panel since the toolbar already has a search button. Let’s add hidden lg:flex to it:
In ProjectTree.astro, replace:
<button class="nav-search-trigger" type="button" id="nav-search-btn">With:
<button class="nav-search-trigger hidden lg:flex" type="button" id="nav-search-btn">And hide the brand h2 in the nav panel too (toolbar has brand):
Replace:
<h2><a href="/" class="nav-heading-link">thalida</a></h2>With:
<h2 class="hidden lg:block"><a href="/" class="nav-heading-link">thalida</a></h2>Step 3: Build to verify
Run: just app::build
Expected: Build succeeds
Step 4: Commit
git add app/src/components/ProjectTree/ProjectTree.css app/src/components/ProjectTree/ProjectTree.astrogit commit -m "refactor(nav): remove ProjectTree media queries, add Tailwind responsive classes"Task 5: CommandPalette — Remove media queries, add responsive classes
Files:
- Modify:
app/src/components/CommandPalette/CommandPalette.css:126-138 - Modify:
app/src/components/CommandPalette/CommandPalette.astro:22,31
Step 1: Remove the media query from CommandPalette.css
Delete lines 126-138:
@media (max-width: 767px) { .cp-overlay--open { @apply items-start; }
.cp-dialog { @apply max-w-full rounded-none max-h-1/2 mt-12; }
.cp-footer { @apply hidden; }}Step 2: Update .cp-dialog with responsive classes
Replace:
.cp-dialog { @apply relative w-full max-w-lg bg-surface rounded-lg overflow-hidden font-body flex flex-col max-h-3/5 shadow-dialog;}With:
.cp-dialog { @apply relative w-full max-w-full mt-12 rounded-none md:max-w-lg md:mt-0 md:rounded-lg bg-surface overflow-hidden font-body flex flex-col max-h-1/2 md:max-h-3/5 shadow-dialog;}Step 3: Update .cp-overlay--open with responsive classes
Replace:
.cp-overlay--open { @apply flex items-center justify-center;}With:
.cp-overlay--open { @apply flex items-start md:items-center justify-center;}Step 4: Update .cp-footer with responsive classes
Replace:
.cp-footer { @apply flex gap-4 py-2 px-4 border-t border-border text-xs text-muted;}With:
.cp-footer { @apply hidden md:flex gap-4 py-2 px-4 border-t border-border text-xs text-muted;}Step 5: Build to verify
Run: just app::build
Expected: Build succeeds
Step 6: Commit
git add app/src/components/CommandPalette/CommandPalette.cssgit commit -m "refactor(command-palette): replace media queries with Tailwind responsive utilities"Task 6: CollectionGrid — Remove media queries, add responsive classes
Files:
- Modify:
app/src/components/CollectionGrid/CollectionGrid.css:49-236
Step 1: Update .items-grid with responsive columns
Replace:
.items-grid { @apply grid gap-4 grid-cols-2;}With:
.items-grid { @apply grid gap-4 grid-cols-1 sm:grid-cols-2 2xl:grid-cols-3;}Step 2: Update masonry mobile fallback
The masonry JS in CollectionGrid.astro already checks cols <= 1 and disables masonry. With grid-cols-1 as the default (mobile), this works automatically. No JS changes needed.
Step 3: Delete the @media (min-width: 1536px) block (lines 221-225)
Delete:
@media (min-width: 1536px) { .items-grid { @apply grid-cols-3; }}Step 4: Delete the @media (max-width: 639px) block (lines 227-236)
Delete:
@media (max-width: 639px) { .items-grid { @apply grid-cols-1; }
.items-grid.masonry-ready { @apply gap-4; grid-auto-rows: auto; }}Step 5: Add masonry single-column override to the base .masonry-ready rule
The masonry JS already handles disabling masonry on single-column. But we should ensure the CSS doesn’t break. The existing .items-grid.masonry-ready rule sets gap-y-0 and grid-auto-rows: 10px, which is wrong for single-column. The JS removes the masonry-ready class on mobile, so this is fine — no CSS change needed.
Step 6: Build to verify
Run: just app::build
Expected: Build succeeds
Step 7: Commit
git add app/src/components/CollectionGrid/CollectionGrid.cssgit commit -m "refactor(grid): replace CollectionGrid media queries with Tailwind responsive utilities"Task 7: Page-specific styles — index.astro and about.astro
Files:
- Modify:
app/src/pages/index.astro:196-206 - Modify:
app/src/pages/about.astro:113-121
Step 1: Replace index.astro media query with responsive class
Replace the .home-projects block and its media query (lines 197-206):
.home-projects { @apply grid gap-4; grid-template-columns: 1fr; }
@media (min-width: 768px) { .home-projects { grid-template-columns: repeat(3, 1fr); } }With:
.home-projects { @apply grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3; }Step 2: Replace about.astro media query with responsive class
Replace the .about-photos block and its media query (lines 113-121):
.about-photos { @apply grid grid-cols-4 gap-2 mb-6; }
@media (max-width: 767px) { .about-photos { @apply grid-cols-2; } }With:
.about-photos { @apply grid grid-cols-2 sm:grid-cols-4 gap-2 mb-6; }Step 3: Build to verify
Run: just app::build
Expected: Build succeeds
Step 4: Commit
git add app/src/pages/index.astro app/src/pages/about.astrogit commit -m "refactor(pages): replace page-specific media queries with Tailwind responsive utilities"Task 8: Final build and verification
Step 1: Run a full build
Run: just app::build
Expected: Build succeeds with zero errors
Step 2: Verify no remaining layout media queries
Run: grep -rn "@media" app/src/ --include="*.css" --include="*.astro" | grep -v "prefers-color-scheme"
Expected: No results (only the prefers-color-scheme query in live-window.css should remain)
Step 3: Run all tests
Run: just test
Expected: All tests pass
Step 4: Start the dev server for visual verification
Run: just app::serve
Expected: Site loads. Manually verify:
- Desktop (1024px+): Three columns, same as before
- Tablet (768px–1023px): Content + chat visible, header bar with hamburger, slide-down nav panel works
- Mobile (< 768px): Single column, header bar, slide-down nav, floating chat FAB, chat overlay works
Step 5: Final commit if any fixes needed
If visual testing reveals issues, fix and commit individually.