This commit is contained in:
Michael Freno
2025-12-18 00:55:21 -05:00
parent 14da7f9912
commit 1142d6f126
4 changed files with 231 additions and 32 deletions

View File

@@ -1,6 +1,6 @@
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { createEffect, ErrorBoundary, Suspense } from "solid-js";
import { createEffect, ErrorBoundary, Suspense, onMount, onCleanup } from "solid-js";
import "./app.css";
import { LeftBar, RightBar } from "./components/Bars";
import { TerminalSplash } from "./components/TerminalSplash";
@@ -10,14 +10,37 @@ import ErrorBoundaryFallback from "./components/ErrorBoundaryFallback";
import { BarsProvider, useBars } from "./context/bars";
function AppLayout(props: { children: any }) {
const { leftBarSize, rightBarSize, setCenterWidth, centerWidth } = useBars();
const {
leftBarSize,
rightBarSize,
setCenterWidth,
centerWidth,
leftBarVisible,
rightBarVisible,
toggleLeftBar,
toggleRightBar,
setLeftBarVisible,
setRightBarVisible
} = useBars();
let lastScrollY = 0;
const SCROLL_THRESHOLD = 100;
createEffect(() => {
const handleResize = () => {
const isMobile = window.innerWidth < 768; // md breakpoint
// Show bars when switching to desktop
if (!isMobile) {
setLeftBarVisible(true);
setRightBarVisible(true);
}
const newWidth = window.innerWidth - leftBarSize() - rightBarSize();
setCenterWidth(newWidth);
};
// Call immediately and whenever dependencies change
handleResize();
window.addEventListener("resize", handleResize);
@@ -25,8 +48,98 @@ function AppLayout(props: { children: any }) {
return () => window.removeEventListener("resize", handleResize);
});
// Recalculate when bar sizes change (visibility or actual resize)
createEffect(() => {
const newWidth = window.innerWidth - leftBarSize() - rightBarSize();
setCenterWidth(newWidth);
});
// Auto-hide on scroll (mobile only)
onMount(() => {
const handleScroll = () => {
const currentScrollY = window.scrollY;
const isMobile = window.innerWidth < 768; // md breakpoint
if (isMobile && currentScrollY > SCROLL_THRESHOLD) {
// Scrolling down past threshold - hide left bar on mobile
if (currentScrollY > lastScrollY) {
setLeftBarVisible(false);
}
}
lastScrollY = currentScrollY;
};
window.addEventListener("scroll", handleScroll, { passive: true });
onCleanup(() => {
window.removeEventListener("scroll", handleScroll);
});
});
// Swipe gestures to reveal bars
onMount(() => {
let touchStartX = 0;
let touchStartY = 0;
const EDGE_THRESHOLD = 50; // pixels from edge to trigger
const SWIPE_THRESHOLD = 100; // minimum swipe distance
const handleTouchStart = (e: TouchEvent) => {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
};
const handleTouchEnd = (e: TouchEvent) => {
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const deltaX = touchEndX - touchStartX;
const deltaY = touchEndY - touchStartY;
const isMobile = window.innerWidth < 768; // md breakpoint
// Only trigger if horizontal swipe is dominant
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Mobile: Only left bar
if (isMobile) {
// Swipe right from left edge - reveal left bar
if (touchStartX < EDGE_THRESHOLD && deltaX > SWIPE_THRESHOLD) {
setLeftBarVisible(true);
}
} else {
// Desktop: Both bars
// Swipe right from left edge - reveal left bar
if (touchStartX < EDGE_THRESHOLD && deltaX > SWIPE_THRESHOLD) {
setLeftBarVisible(true);
}
// Swipe left from right edge - reveal right bar
else if (touchStartX > window.innerWidth - EDGE_THRESHOLD && deltaX < -SWIPE_THRESHOLD) {
setRightBarVisible(true);
}
}
}
};
window.addEventListener("touchstart", handleTouchStart, { passive: true });
window.addEventListener("touchend", handleTouchEnd, { passive: true });
onCleanup(() => {
window.removeEventListener("touchstart", handleTouchStart);
window.removeEventListener("touchend", handleTouchEnd);
});
});
return (
<div class="flex max-w-screen flex-row">
{/* Backdrop overlay - visible on mobile when sidebar is open */}
<div
class="fixed inset-0 bg-black transition-opacity duration-500 ease-out md:hidden z-40"
classList={{
"opacity-50 pointer-events-auto": leftBarVisible(),
"opacity-0 pointer-events-none": !leftBarVisible()
}}
onClick={() => setLeftBarVisible(false)}
aria-label="Close sidebar"
/>
<LeftBar />
<div
style={{

View File

@@ -1,30 +1,52 @@
import { Typewriter } from "./Typewriter";
import { useBars } from "~/context/bars";
import { createEffect, onMount } from "solid-js";
import { onMount, createEffect } from "solid-js";
export function LeftBar() {
const { setLeftBarSize } = useBars();
const { setLeftBarSize, leftBarVisible } = useBars();
let ref: HTMLDivElement | undefined;
let actualWidth = 0;
onMount(() => {
if (ref) {
const updateSize = () => {
setLeftBarSize(ref?.offsetWidth || 0);
actualWidth = ref?.offsetWidth || 0;
setLeftBarSize(leftBarVisible() ? actualWidth : 0);
};
updateSize();
const resizeObserver = new ResizeObserver(updateSize);
const resizeObserver = new ResizeObserver((entries) => {
// Use requestAnimationFrame to avoid ResizeObserver loop error
requestAnimationFrame(() => {
actualWidth = ref?.offsetWidth || 0;
setLeftBarSize(leftBarVisible() ? actualWidth : 0);
});
});
resizeObserver.observe(ref);
return () => resizeObserver.disconnect();
}
});
// Update size when visibility changes
createEffect(() => {
setLeftBarSize(leftBarVisible() ? actualWidth : 0);
});
return (
<nav
ref={ref}
class="border-r-overlay2 fixed h-full min-h-screen w-fit max-w-[25%] border-r-2"
class="border-r-overlay2 fixed h-full min-h-screen w-fit max-w-[25%] border-r-2 transition-transform duration-500 ease-out z-50"
classList={{
"-translate-x-full": !leftBarVisible(),
"translate-x-0": leftBarVisible()
}}
style={{
"transition-timing-function": leftBarVisible()
? "cubic-bezier(0.34, 1.56, 0.64, 1)" // Bounce out when revealing
: "cubic-bezier(0.4, 0, 0.2, 1)" // Smooth when hiding
}}
>
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
<h3 class="hover:text-subtext0 w-fit text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
@@ -37,17 +59,31 @@ export function LeftBar() {
{/*TODO:Grab and render 5 most recent blog posts here */}
<li></li>
</ul>
<ul class="gap-4">
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="/">Home</a>
</li>
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="/blog">Blog</a>
</li>
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#services">Services</a>
</li>
</ul>
<div class="flex flex-col gap-4">
<ul class="gap-4">
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="/">Home</a>
</li>
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="/blog">Blog</a>
</li>
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#services">Services</a>
</li>
</ul>
{/* Right bar navigation merged for mobile */}
<ul class="gap-4 md:hidden border-t border-overlay0 pt-4">
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#home">Home</a>
</li>
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#about">About</a>
</li>
<li class="hover:text-subtext0 w-fit transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-110 hover:font-bold">
<a href="#services">Services</a>
</li>
</ul>
</div>
</div>
</Typewriter>
</nav>
@@ -55,28 +91,51 @@ export function LeftBar() {
}
export function RightBar() {
const { setRightBarSize } = useBars();
const { setRightBarSize, rightBarVisible } = useBars();
let ref: HTMLDivElement | undefined;
let actualWidth = 0;
onMount(() => {
if (ref) {
const updateSize = () => {
setRightBarSize(ref?.offsetWidth || 0);
actualWidth = ref?.offsetWidth || 0;
setRightBarSize(rightBarVisible() ? actualWidth : 0);
};
updateSize();
const resizeObserver = new ResizeObserver(updateSize);
const resizeObserver = new ResizeObserver((entries) => {
// Use requestAnimationFrame to avoid ResizeObserver loop error
requestAnimationFrame(() => {
actualWidth = ref?.offsetWidth || 0;
setRightBarSize(rightBarVisible() ? actualWidth : 0);
});
});
resizeObserver.observe(ref);
return () => resizeObserver.disconnect();
}
});
// Update size when visibility changes
createEffect(() => {
setRightBarSize(rightBarVisible() ? actualWidth : 0);
});
return (
<nav
ref={ref}
class="border-l-overlay2 fixed right-0 h-full min-h-screen w-fit max-w-[25%] border-l-2">
class="border-l-overlay2 fixed right-0 h-full min-h-screen w-fit max-w-[25%] border-l-2 transition-transform duration-500 ease-out md:block hidden z-50"
classList={{
"translate-x-full": !rightBarVisible(),
"translate-x-0": rightBarVisible()
}}
style={{
"transition-timing-function": rightBarVisible()
? "cubic-bezier(0.34, 1.56, 0.64, 1)" // Bounce out when revealing
: "cubic-bezier(0.4, 0, 0.2, 1)" // Smooth when hiding
}}
>
<Typewriter keepAlive={false} class="z-50">
<h3 class="hover:text-subtext0 w-fit text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
Right Navigation

View File

@@ -8,13 +8,25 @@ const BarsContext = createContext<{
setRightBarSize: (size: number) => void;
centerWidth: Accessor<number>;
setCenterWidth: (size: number) => void;
leftBarVisible: Accessor<boolean>;
setLeftBarVisible: (visible: boolean) => void;
rightBarVisible: Accessor<boolean>;
setRightBarVisible: (visible: boolean) => void;
toggleLeftBar: () => void;
toggleRightBar: () => void;
}>({
leftBarSize: () => 0,
setLeftBarSize: () => {},
rightBarSize: () => 0,
setRightBarSize: () => {},
centerWidth: () => 0,
setCenterWidth: () => {}
setCenterWidth: () => {},
leftBarVisible: () => true,
setLeftBarVisible: () => {},
rightBarVisible: () => true,
setRightBarVisible: () => {},
toggleLeftBar: () => {},
toggleRightBar: () => {}
});
export function useBars() {
@@ -26,6 +38,11 @@ export function BarsProvider(props: { children: any }) {
const [leftBarSize, setLeftBarSize] = createSignal(0);
const [rightBarSize, setRightBarSize] = createSignal(0);
const [centerWidth, setCenterWidth] = createSignal(0);
const [leftBarVisible, setLeftBarVisible] = createSignal(true);
const [rightBarVisible, setRightBarVisible] = createSignal(true);
const toggleLeftBar = () => setLeftBarVisible(!leftBarVisible());
const toggleRightBar = () => setRightBarVisible(!rightBarVisible());
return (
<BarsContext.Provider
@@ -35,7 +52,13 @@ export function BarsProvider(props: { children: any }) {
rightBarSize,
setRightBarSize,
centerWidth,
setCenterWidth
setCenterWidth,
leftBarVisible,
setLeftBarVisible,
rightBarVisible,
setRightBarVisible,
toggleLeftBar,
toggleRightBar
}}
>
{props.children}

View File

@@ -186,8 +186,12 @@ export default function PostPage() {
/>
</div>
<div
class="text-shadow absolute top-1/2 left-1/2 z-10 w-full -translate-x-1/2 -translate-y-1/2 px-4 text-center tracking-widest text-white brightness-150 select-text"
style={{ "pointer-events": "none" }}
class="text-shadow absolute top-1/3 z-10 my-auto px-4 text-center tracking-widest text-white brightness-150 select-text"
style={{
"pointer-events": "none",
width: `${centerWidth()}px`,
"margin-left": `${leftBarSize()}px`
}}
>
<div class="text-3xl font-light tracking-widest">
{p().title.replaceAll("_", " ")}