favicon update
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 287 KiB |
BIN
public/apple-touch-icon.png
Executable file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/favicon-96x96.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/favicon.ico
Normal file → Executable file
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 15 KiB |
17
public/favicon.svg
Executable file
|
After Width: | Height: | Size: 1.8 MiB |
21
public/site.webmanifest
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "MyWebSite",
|
||||||
|
"short_name": "MySite",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/web-app-manifest-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/web-app-manifest-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
BIN
public/web-app-manifest-192x192.png
Executable file
|
After Width: | Height: | Size: 51 KiB |
BIN
public/web-app-manifest-512x512.png
Executable file
|
After Width: | Height: | Size: 287 KiB |
@@ -236,7 +236,6 @@ body {
|
|||||||
background-color: var(--color-text);
|
background-color: var(--color-text);
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Block cursor when done typing */
|
/* Block cursor when done typing */
|
||||||
@@ -247,7 +246,6 @@ body {
|
|||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
animation: blink 1s infinite;
|
animation: blink 1s infinite;
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes blink {
|
@keyframes blink {
|
||||||
|
|||||||
22
src/app.tsx
@@ -106,11 +106,10 @@ function AppLayout(props: { children: any }) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Swipe gestures to reveal bars
|
// Global swipe gestures to reveal/hide bars
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let touchStartX = 0;
|
let touchStartX = 0;
|
||||||
let touchStartY = 0;
|
let touchStartY = 0;
|
||||||
const EDGE_THRESHOLD = 100;
|
|
||||||
const SWIPE_THRESHOLD = 100;
|
const SWIPE_THRESHOLD = 100;
|
||||||
|
|
||||||
const handleTouchStart = (e: TouchEvent) => {
|
const handleTouchStart = (e: TouchEvent) => {
|
||||||
@@ -129,21 +128,22 @@ function AppLayout(props: { children: any }) {
|
|||||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||||
// Mobile: Only left bar
|
// Mobile: Only left bar
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
// Swipe right from left edge - reveal left bar
|
// Swipe right anywhere - reveal left bar
|
||||||
if (touchStartX < EDGE_THRESHOLD && deltaX > SWIPE_THRESHOLD) {
|
if (deltaX > SWIPE_THRESHOLD) {
|
||||||
setLeftBarVisible(true);
|
setLeftBarVisible(true);
|
||||||
}
|
}
|
||||||
|
// Swipe left anywhere - hide left bar
|
||||||
|
else if (deltaX < -SWIPE_THRESHOLD) {
|
||||||
|
setLeftBarVisible(false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Desktop: Both bars
|
// Desktop: Both bars
|
||||||
// Swipe right from left edge - reveal left bar
|
// Swipe right anywhere - reveal left bar
|
||||||
if (touchStartX < EDGE_THRESHOLD && deltaX > SWIPE_THRESHOLD) {
|
if (deltaX > SWIPE_THRESHOLD) {
|
||||||
setLeftBarVisible(true);
|
setLeftBarVisible(true);
|
||||||
}
|
}
|
||||||
// Swipe left from right edge - reveal right bar
|
// Swipe left anywhere - reveal right bar
|
||||||
else if (
|
else if (deltaX < -SWIPE_THRESHOLD) {
|
||||||
touchStartX > window.innerWidth - EDGE_THRESHOLD &&
|
|
||||||
deltaX < -SWIPE_THRESHOLD
|
|
||||||
) {
|
|
||||||
setRightBarVisible(true);
|
setRightBarVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,8 +164,6 @@ export function LeftBar() {
|
|||||||
useBars();
|
useBars();
|
||||||
let ref: HTMLDivElement | undefined;
|
let ref: HTMLDivElement | undefined;
|
||||||
let actualWidth = 0;
|
let actualWidth = 0;
|
||||||
let touchStartX = 0;
|
|
||||||
let touchStartY = 0;
|
|
||||||
|
|
||||||
const [recentPosts, setRecentPosts] = createSignal<any[] | undefined>(
|
const [recentPosts, setRecentPosts] = createSignal<any[] | undefined>(
|
||||||
undefined
|
undefined
|
||||||
@@ -191,10 +189,8 @@ export function LeftBar() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Mark as mounted to avoid hydration mismatch
|
|
||||||
setIsMounted(true);
|
setIsMounted(true);
|
||||||
|
|
||||||
// Setup ResizeObserver FIRST (synchronous) - this allows bar sizing to happen immediately
|
|
||||||
if (ref) {
|
if (ref) {
|
||||||
const updateSize = () => {
|
const updateSize = () => {
|
||||||
actualWidth = ref?.offsetWidth || 0;
|
actualWidth = ref?.offsetWidth || 0;
|
||||||
@@ -212,30 +208,6 @@ export function LeftBar() {
|
|||||||
});
|
});
|
||||||
resizeObserver.observe(ref);
|
resizeObserver.observe(ref);
|
||||||
|
|
||||||
// Swipe-to-dismiss gesture on sidebar itself (mobile only)
|
|
||||||
const handleTouchStart = (e: TouchEvent) => {
|
|
||||||
touchStartX = e.touches[0].clientX;
|
|
||||||
touchStartY = e.touches[0].clientY;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTouchEnd = (e: TouchEvent) => {
|
|
||||||
const isMobile = window.innerWidth < 768;
|
|
||||||
if (!isMobile) return; // Only allow dismiss on mobile
|
|
||||||
|
|
||||||
const touchEndX = e.changedTouches[0].clientX;
|
|
||||||
const touchEndY = e.changedTouches[0].clientY;
|
|
||||||
const deltaX = touchEndX - touchStartX;
|
|
||||||
const deltaY = touchEndY - touchStartY;
|
|
||||||
|
|
||||||
// Only trigger if horizontal swipe is dominant
|
|
||||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
|
||||||
// Swipe left to dismiss (at least 50px)
|
|
||||||
if (deltaX < -50 && leftBarVisible()) {
|
|
||||||
setLeftBarVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Focus trap for accessibility on mobile
|
// Focus trap for accessibility on mobile
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
const isMobile = window.innerWidth < 768;
|
const isMobile = window.innerWidth < 768;
|
||||||
@@ -270,14 +242,10 @@ export function LeftBar() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ref.addEventListener("touchstart", handleTouchStart, { passive: true });
|
|
||||||
ref.addEventListener("touchend", handleTouchEnd, { passive: true });
|
|
||||||
ref.addEventListener("keydown", handleKeyDown);
|
ref.addEventListener("keydown", handleKeyDown);
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
ref?.removeEventListener("touchstart", handleTouchStart);
|
|
||||||
ref?.removeEventListener("touchend", handleTouchEnd);
|
|
||||||
ref?.removeEventListener("keydown", handleKeyDown);
|
ref?.removeEventListener("keydown", handleKeyDown);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -348,17 +316,17 @@ export function LeftBar() {
|
|||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
ref={ref}
|
ref={ref}
|
||||||
class="border-r-overlay2 bg-base fixed z-50 h-full w-min border-r-2 transition-transform duration-500 ease-out md:max-w-[20%]"
|
class="border-r-overlay2 bg-base fixed z-50 h-dvh w-min border-r-2 transition-transform duration-500 ease-out md:max-w-[20%]"
|
||||||
classList={{
|
classList={{
|
||||||
"-translate-x-full": !leftBarVisible(),
|
"-translate-x-full": !leftBarVisible(),
|
||||||
"translate-x-0": leftBarVisible()
|
"translate-x-0": leftBarVisible()
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"transition-timing-function": leftBarVisible()
|
"transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", // Smooth for both revealing and hiding
|
||||||
? "cubic-bezier(0.34, 1.56, 0.64, 1)" // Bounce out when revealing
|
|
||||||
: "cubic-bezier(0.4, 0, 0.2, 1)", // Smooth when hiding
|
|
||||||
"min-width": leftBarSize() > 0 ? `${leftBarSize()}px` : undefined,
|
"min-width": leftBarSize() > 0 ? `${leftBarSize()}px` : undefined,
|
||||||
"box-shadow": "inset -6px 0 16px -6px rgba(0, 0, 0, 0.1)"
|
"box-shadow": "inset -6px 0 16px -6px rgba(0, 0, 0, 0.1)",
|
||||||
|
"padding-top": "env(safe-area-inset-top)",
|
||||||
|
"padding-bottom": "env(safe-area-inset-bottom)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Hamburger menu button - positioned at right edge of navbar */}
|
{/* Hamburger menu button - positioned at right edge of navbar */}
|
||||||
@@ -388,7 +356,7 @@ export function LeftBar() {
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="flex h-full min-h-full flex-col overflow-y-auto">
|
<div class="flex h-full flex-col overflow-y-auto">
|
||||||
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
|
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
|
||||||
<h3 class="hover:text-subtext0 w-fit pt-6 text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
|
<h3 class="hover:text-subtext0 w-fit pt-6 text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
|
||||||
<a href="/">{formatDomainName(env.VITE_DOMAIN)}</a>
|
<a href="/">{formatDomainName(env.VITE_DOMAIN)}</a>
|
||||||
@@ -541,17 +509,17 @@ export function RightBar() {
|
|||||||
return (
|
return (
|
||||||
<nav
|
<nav
|
||||||
ref={ref}
|
ref={ref}
|
||||||
class="border-l-overlay2 bg-base fixed right-0 z-50 hidden h-full min-h-screen w-fit border-l-2 transition-transform duration-500 ease-out md:block md:max-w-[20%]"
|
class="border-l-overlay2 bg-base fixed right-0 z-50 hidden h-dvh w-fit border-l-2 transition-transform duration-500 ease-out md:block md:max-w-[20%]"
|
||||||
classList={{
|
classList={{
|
||||||
"translate-x-full": !rightBarVisible(),
|
"translate-x-full": !rightBarVisible(),
|
||||||
"translate-x-0": rightBarVisible()
|
"translate-x-0": rightBarVisible()
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
"transition-timing-function": rightBarVisible()
|
"transition-timing-function": "cubic-bezier(0.4, 0, 0.2, 1)", // Smooth for both revealing and hiding
|
||||||
? "cubic-bezier(0.34, 1.56, 0.64, 1)"
|
|
||||||
: "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
||||||
"min-width": rightBarSize() > 0 ? `${rightBarSize()}px` : undefined,
|
"min-width": rightBarSize() > 0 ? `${rightBarSize()}px` : undefined,
|
||||||
"box-shadow": "inset 6px 0 16px -6px rgba(0, 0, 0, 0.1)"
|
"box-shadow": "inset 6px 0 16px -6px rgba(0, 0, 0, 0.1)",
|
||||||
|
"padding-top": "env(safe-area-inset-top)",
|
||||||
|
"padding-bottom": "env(safe-area-inset-bottom)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RightBarContent />
|
<RightBarContent />
|
||||||
|
|||||||