Compare commits
3 Commits
49fc7b5331
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
091f97968b | ||
|
|
f1668f08f2 | ||
|
|
3fe1b0e6d5 |
11
LOG.md
11
LOG.md
@@ -1,5 +1,12 @@
|
||||
# Work Log
|
||||
|
||||
## 2026-06-06
|
||||
|
||||
- Fixed the desktop Dock so route changes from Dock menu links no longer leave focus-held open state behind when the Dock is not pinned.
|
||||
- Kept the saved `dock-pinned-v2` preference unchanged during navigation and only released transient Dock focus/hover state after menu clicks and pathname changes.
|
||||
- Validation: `npm run lint` passed and `npm run build` passed. In-app browser verification on `http://localhost:3111/` confirmed that after unpinning the Dock, clicking the Dock archive menu navigates to `/archive`, the pin button remains `aria-pressed=false`, and the Dock collapses after the pointer leaves.
|
||||
- Recommended next task: do one visual pass on the Dock at narrow desktop widths to confirm the collapse animation still feels smooth around the sidebar offset.
|
||||
|
||||
## 2026-06-02
|
||||
|
||||
- Refined the public layout after desktop/mobile review: the shell now guards horizontal overflow, uses route-aware bottom padding, hides the top menubar on mobile, and opens the mobile sidebar/search panel from the Dock.
|
||||
@@ -9,6 +16,8 @@
|
||||
- Softened the Dock button palette to quieter pastel tones and made the light-mode Dock bar read as a cleaner white glass surface.
|
||||
- Fixed the Dock pin toggle so mouse-based unpinning no longer leaves button focus holding the Dock open, and changed the Dock Home button to a white neutral tone.
|
||||
- Reworked the Dock material away from cute pastel tiles into a more macOS-like neutral frosted glass texture with translucent white glass buttons.
|
||||
- Corrected the Dock button direction so only Home stays white, while the other actions keep subtle pastel tints with a translucent macOS-style glass material.
|
||||
- Increased Dock button translucency contrast so the buttons remain glassy while sitting slightly more opaque than the Dock background.
|
||||
- Rebalanced the home page around latest posts with popular posts as a supporting panel, converted the post detail page into a quieter reader surface, and hardened Markdown/code/table wrapping against mobile overflow.
|
||||
- Added a client-side archive explorer with year, month, category, and compact/comfort density controls over the existing server-fetched post list.
|
||||
- Rebuilt the chess page with safer viewport-aware board sizing, quieter puzzle panels, and enough layout clearance for the Dock.
|
||||
@@ -18,6 +27,8 @@
|
||||
- Validation: `npm run lint` passed and `npm run build` passed. In-app browser verification on `http://localhost:3106/` confirmed the light-mode Dock bar renders as a cleaner white glass surface and the Dock button colors use quieter pastel tones.
|
||||
- Validation: `npm run lint` passed and `npm run build` passed. In-app browser verification on `http://localhost:3107/` confirmed the Dock Home button renders white and mouse-based unpinning collapses the Dock after the pointer leaves.
|
||||
- Validation: `npm run lint` passed and `npm run build` passed. In-app browser verification on `http://localhost:3108/` confirmed the Dock uses neutral translucent glass material, with non-home buttons rendering as subtle white glass rather than pastel color tiles.
|
||||
- Validation: `npm run lint` passed and `npm run build` passed. In-app browser verification on `http://localhost:3109/` confirmed Home stays white while the other Dock buttons keep pastel-tinted translucent glass backgrounds with button-level blur.
|
||||
- Validation: `npm run lint` passed and `npm run build` passed. In-app browser verification on `http://localhost:3110/` confirmed Dock buttons remain translucent but render slightly more opaque than the Dock background for clearer contrast.
|
||||
- Recommended next task: run a browser pass against a reachable backend API, especially one real post detail route and the populated chess puzzle board, then tune any remaining content-specific long-title or board-height edge cases.
|
||||
|
||||
## 2026-05-29
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { type CSSProperties, type FocusEvent, useEffect, useRef, useState } from 'react';
|
||||
import { type CSSProperties, type FocusEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { clsx } from 'clsx';
|
||||
@@ -88,9 +88,9 @@ const dockGlassTone = createDockTone(
|
||||
);
|
||||
|
||||
const homeDockTone = createDockTone(
|
||||
'rgba(255, 255, 255, 0.36)',
|
||||
'rgba(255, 255, 255, 0.5)',
|
||||
'rgba(255, 255, 255, 0.62)',
|
||||
'rgba(255, 255, 255, 0.58)',
|
||||
'rgba(255, 255, 255, 0.68)',
|
||||
'rgba(255, 255, 255, 0.76)',
|
||||
'rgba(255, 255, 255, 0.28)',
|
||||
'rgba(255, 255, 255, 0.46)',
|
||||
'var(--color-text-muted)',
|
||||
@@ -100,16 +100,16 @@ const homeDockTone = createDockTone(
|
||||
|
||||
const dockToneStyles: Record<string, DockToneStyle> = {
|
||||
home: homeDockTone,
|
||||
archive: dockGlassTone,
|
||||
chess: dockGlassTone,
|
||||
admin: dockGlassTone,
|
||||
write: dockGlassTone,
|
||||
login: dockGlassTone,
|
||||
signup: dockGlassTone,
|
||||
logout: dockGlassTone,
|
||||
pin: dockGlassTone,
|
||||
menu: dockGlassTone,
|
||||
more: dockGlassTone,
|
||||
archive: createDockTone('rgba(222, 216, 255, 0.54)', 'rgba(222, 216, 255, 0.64)', 'rgba(222, 216, 255, 0.72)', 'rgba(255, 255, 255, 0.26)', 'rgba(151, 132, 214, 0.34)', '#6f668f', '#514274', 'rgba(151, 132, 214, 0.2)'),
|
||||
chess: createDockTone('rgba(255, 229, 162, 0.54)', 'rgba(255, 229, 162, 0.64)', 'rgba(255, 229, 162, 0.72)', 'rgba(255, 255, 255, 0.28)', 'rgba(184, 134, 42, 0.34)', '#7b6d4a', '#665125', 'rgba(184, 134, 42, 0.2)'),
|
||||
admin: createDockTone('rgba(192, 226, 255, 0.54)', 'rgba(192, 226, 255, 0.64)', 'rgba(192, 226, 255, 0.72)', 'rgba(255, 255, 255, 0.27)', 'rgba(81, 145, 195, 0.34)', '#627f96', '#3f6686', 'rgba(81, 145, 195, 0.2)'),
|
||||
write: createDockTone('rgba(255, 209, 184, 0.54)', 'rgba(255, 209, 184, 0.64)', 'rgba(255, 209, 184, 0.72)', 'rgba(255, 255, 255, 0.28)', 'rgba(187, 106, 70, 0.34)', '#8c705f', '#724d38', 'rgba(187, 106, 70, 0.2)'),
|
||||
login: createDockTone('rgba(190, 236, 238, 0.54)', 'rgba(190, 236, 238, 0.64)', 'rgba(190, 236, 238, 0.72)', 'rgba(255, 255, 255, 0.27)', 'rgba(68, 150, 158, 0.34)', '#63898c', '#446d72', 'rgba(68, 150, 158, 0.2)'),
|
||||
signup: createDockTone('rgba(237, 207, 250, 0.54)', 'rgba(237, 207, 250, 0.64)', 'rgba(237, 207, 250, 0.72)', 'rgba(255, 255, 255, 0.27)', 'rgba(153, 91, 176, 0.34)', '#846c8e', '#684a78', 'rgba(153, 91, 176, 0.2)'),
|
||||
logout: createDockTone('rgba(255, 207, 216, 0.54)', 'rgba(255, 207, 216, 0.64)', 'rgba(255, 207, 216, 0.72)', 'rgba(255, 255, 255, 0.28)', 'rgba(188, 84, 99, 0.34)', '#8f6970', '#714850', 'rgba(188, 84, 99, 0.2)'),
|
||||
pin: createDockTone('rgba(255, 219, 170, 0.56)', 'rgba(255, 219, 170, 0.66)', 'rgba(255, 219, 170, 0.74)', 'rgba(255, 255, 255, 0.28)', 'rgba(184, 118, 44, 0.36)', '#8a704e', '#6b4f2d', 'rgba(184, 118, 44, 0.22)'),
|
||||
menu: createDockTone('rgba(213, 234, 203, 0.54)', 'rgba(213, 234, 203, 0.64)', 'rgba(213, 234, 203, 0.72)', 'rgba(255, 255, 255, 0.27)', 'rgba(98, 143, 80, 0.34)', '#6f8366', '#526f44', 'rgba(98, 143, 80, 0.2)'),
|
||||
more: createDockTone('rgba(210, 228, 255, 0.54)', 'rgba(210, 228, 255, 0.64)', 'rgba(210, 228, 255, 0.72)', 'rgba(255, 255, 255, 0.27)', 'rgba(86, 126, 183, 0.34)', '#6d7f98', '#4f688d', 'rgba(86, 126, 183, 0.2)'),
|
||||
default: dockGlassTone,
|
||||
};
|
||||
|
||||
@@ -124,6 +124,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
const [isDockHovered, setIsDockHovered] = useState(false);
|
||||
const [isDockFocused, setIsDockFocused] = useState(false);
|
||||
const [isDockClosing, setIsDockClosing] = useState(false);
|
||||
const isPinnedRef = useRef(isPinned);
|
||||
const dockZoneRef = useRef<HTMLDivElement>(null);
|
||||
const dockLeaveTimerRef = useRef<number | null>(null);
|
||||
const dockCollapseTimerRef = useRef<number | null>(null);
|
||||
@@ -132,7 +133,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
const isDockOpen = isPinned || isDockHovered || isDockFocused;
|
||||
const isDockRangeExpanded = isDockOpen || isDockClosing;
|
||||
|
||||
const clearDockTimers = () => {
|
||||
const clearDockTimers = useCallback(() => {
|
||||
if (dockLeaveTimerRef.current !== null) {
|
||||
window.clearTimeout(dockLeaveTimerRef.current);
|
||||
dockLeaveTimerRef.current = null;
|
||||
@@ -142,7 +143,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
window.clearTimeout(dockCollapseTimerRef.current);
|
||||
dockCollapseTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const expandDock = () => {
|
||||
clearDockTimers();
|
||||
@@ -150,7 +151,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
setIsDockHovered(true);
|
||||
};
|
||||
|
||||
const startDockCollapse = () => {
|
||||
const startDockCollapse = useCallback(() => {
|
||||
clearDockTimers();
|
||||
setIsDockHovered(false);
|
||||
setIsDockClosing(true);
|
||||
@@ -158,7 +159,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
setIsDockClosing(false);
|
||||
dockCollapseTimerRef.current = null;
|
||||
}, DOCK_COLLAPSE_MS);
|
||||
};
|
||||
}, [clearDockTimers]);
|
||||
|
||||
const scheduleDockCollapse = () => {
|
||||
if (isPinned || isDockFocused) return;
|
||||
@@ -169,10 +170,44 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
}, DOCK_LEAVE_DELAY_MS);
|
||||
};
|
||||
|
||||
const blurFocusedDockItem = useCallback(() => {
|
||||
if (
|
||||
document.activeElement instanceof HTMLElement &&
|
||||
dockZoneRef.current?.contains(document.activeElement)
|
||||
) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const releaseUnpinnedDockAfterNavigation = useCallback(() => {
|
||||
if (isPinnedRef.current) return;
|
||||
|
||||
const isPointerInsideDock = dockZoneRef.current?.matches(':hover') ?? false;
|
||||
|
||||
blurFocusedDockItem();
|
||||
setIsDockFocused(false);
|
||||
|
||||
if (isPointerInsideDock) {
|
||||
clearDockTimers();
|
||||
setIsDockClosing(false);
|
||||
setIsDockHovered(true);
|
||||
return;
|
||||
}
|
||||
|
||||
startDockCollapse();
|
||||
}, [blurFocusedDockItem, clearDockTimers, startDockCollapse]);
|
||||
|
||||
useEffect(() => {
|
||||
isPinnedRef.current = isPinned;
|
||||
}, [isPinned]);
|
||||
|
||||
useEffect(() => {
|
||||
const frameId = window.requestAnimationFrame(() => {
|
||||
const savedValue = window.localStorage.getItem(DOCK_PINNED_STORAGE_KEY);
|
||||
setIsPinned(savedValue === null ? true : savedValue === 'true');
|
||||
const nextValue = savedValue === null ? true : savedValue === 'true';
|
||||
|
||||
isPinnedRef.current = nextValue;
|
||||
setIsPinned(nextValue);
|
||||
});
|
||||
|
||||
return () => window.cancelAnimationFrame(frameId);
|
||||
@@ -181,14 +216,15 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
useEffect(() => {
|
||||
const frameId = window.requestAnimationFrame(() => {
|
||||
setIsMoreOpen(false);
|
||||
releaseUnpinnedDockAfterNavigation();
|
||||
});
|
||||
|
||||
return () => window.cancelAnimationFrame(frameId);
|
||||
}, [pathname]);
|
||||
}, [pathname, releaseUnpinnedDockAfterNavigation]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearDockTimers();
|
||||
}, []);
|
||||
}, [clearDockTimers]);
|
||||
|
||||
const handlePinnedChange = () => {
|
||||
const nextValue = !isPinned;
|
||||
@@ -198,6 +234,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
pinToggleStartedByPointerRef.current = false;
|
||||
window.localStorage.setItem(DOCK_PINNED_STORAGE_KEY, String(nextValue));
|
||||
clearDockTimers();
|
||||
isPinnedRef.current = nextValue;
|
||||
setIsPinned(nextValue);
|
||||
|
||||
if (nextValue) {
|
||||
@@ -208,13 +245,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
|
||||
if (startedByPointer) {
|
||||
setIsDockFocused(false);
|
||||
|
||||
if (
|
||||
document.activeElement instanceof HTMLElement &&
|
||||
dockZoneRef.current?.contains(document.activeElement)
|
||||
) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
blurFocusedDockItem();
|
||||
}
|
||||
|
||||
if (isPointerInsideDock) {
|
||||
@@ -361,7 +392,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
const active = item.isActive?.(pathname) ?? false;
|
||||
const toneStyle = getDockToneStyle(item.key);
|
||||
const itemClass = clsx(
|
||||
'group/item relative flex h-12 w-12 items-center justify-center rounded-lg border border-[var(--dock-item-border)] bg-[var(--dock-item-bg)] text-[var(--dock-item-fg)] shadow-[var(--shadow-control)] transition duration-150 hover:-translate-y-1 hover:border-[var(--dock-item-border-hover)] hover:bg-[var(--dock-item-bg-hover)] hover:text-[var(--dock-item-fg-strong)] focus-visible:-translate-y-1',
|
||||
'group/item relative flex h-12 w-12 items-center justify-center rounded-lg border border-[var(--dock-item-border)] bg-[var(--dock-item-bg)] text-[var(--dock-item-fg)] shadow-[var(--shadow-control)] backdrop-blur-[18px] transition duration-150 hover:-translate-y-1 hover:border-[var(--dock-item-border-hover)] hover:bg-[var(--dock-item-bg-hover)] hover:text-[var(--dock-item-fg-strong)] focus-visible:-translate-y-1',
|
||||
active && 'border-[var(--dock-item-border-hover)] bg-[var(--dock-item-bg-active)] text-[var(--dock-item-fg-strong)] ring-1 ring-[var(--dock-item-ring)]',
|
||||
);
|
||||
|
||||
@@ -387,6 +418,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
aria-current={active ? 'page' : undefined}
|
||||
className={itemClass}
|
||||
style={toneStyle}
|
||||
onClick={releaseUnpinnedDockAfterNavigation}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
@@ -413,7 +445,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
const active = activeOverride ?? item.isActive?.(pathname) ?? false;
|
||||
const toneStyle = getDockToneStyle(item.key);
|
||||
const itemClass = clsx(
|
||||
'flex h-12 min-w-0 flex-1 flex-col items-center justify-center gap-0.5 rounded-lg border border-[var(--dock-item-border)] bg-[var(--dock-item-bg)] px-1 text-[10px] font-semibold leading-none text-[var(--dock-item-fg)] shadow-[var(--shadow-control)] transition-colors',
|
||||
'flex h-12 min-w-0 flex-1 flex-col items-center justify-center gap-0.5 rounded-lg border border-[var(--dock-item-border)] bg-[var(--dock-item-bg)] px-1 text-[10px] font-semibold leading-none text-[var(--dock-item-fg)] shadow-[var(--shadow-control)] backdrop-blur-[18px] transition-colors',
|
||||
'hover:border-[var(--dock-item-border-hover)] hover:bg-[var(--dock-item-bg-hover)] hover:text-[var(--dock-item-fg-strong)] focus-visible:bg-[var(--dock-item-bg-active)]',
|
||||
active && 'border-[var(--dock-item-border-hover)] bg-[var(--dock-item-bg-active)] text-[var(--dock-item-fg-strong)] ring-1 ring-[var(--dock-item-ring)]',
|
||||
);
|
||||
@@ -521,7 +553,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
}}
|
||||
onClick={handlePinnedChange}
|
||||
className={clsx(
|
||||
'group/item relative ml-1 flex h-12 w-12 items-center justify-center rounded-lg border border-[var(--dock-item-border-hover)] bg-[var(--dock-item-bg-active)] text-[var(--dock-item-fg-strong)] shadow-[var(--shadow-control)] ring-1 ring-[var(--dock-item-ring)] transition duration-150 hover:-translate-y-1 hover:bg-[var(--dock-item-bg-hover)]',
|
||||
'group/item relative ml-1 flex h-12 w-12 items-center justify-center rounded-lg border border-[var(--dock-item-border-hover)] bg-[var(--dock-item-bg-active)] text-[var(--dock-item-fg-strong)] shadow-[var(--shadow-control)] ring-1 ring-[var(--dock-item-ring)] backdrop-blur-[18px] transition duration-150 hover:-translate-y-1 hover:bg-[var(--dock-item-bg-hover)]',
|
||||
isPinned && 'shadow-[var(--shadow-dock)]',
|
||||
)}
|
||||
style={getDockToneStyle('pin')}
|
||||
@@ -550,7 +582,7 @@ export default function DesktopDock({ isSidebarCollapsed, onOpenMobileMenu }: De
|
||||
const active = item.isActive?.(pathname) ?? false;
|
||||
const toneStyle = getDockToneStyle(item.key);
|
||||
const className = clsx(
|
||||
'inline-flex h-10 min-w-0 items-center justify-center gap-2 rounded-lg border border-[var(--dock-item-border)] bg-[var(--dock-item-bg)] px-3 text-sm font-semibold text-[var(--dock-item-fg)] shadow-[var(--shadow-control)] transition-colors hover:border-[var(--dock-item-border-hover)] hover:bg-[var(--dock-item-bg-hover)] hover:text-[var(--dock-item-fg-strong)]',
|
||||
'inline-flex h-10 min-w-0 items-center justify-center gap-2 rounded-lg border border-[var(--dock-item-border)] bg-[var(--dock-item-bg)] px-3 text-sm font-semibold text-[var(--dock-item-fg)] shadow-[var(--shadow-control)] backdrop-blur-[18px] transition-colors hover:border-[var(--dock-item-border-hover)] hover:bg-[var(--dock-item-bg-hover)] hover:text-[var(--dock-item-fg-strong)]',
|
||||
active && 'border-[var(--dock-item-border-hover)] bg-[var(--dock-item-bg-active)] text-[var(--dock-item-fg-strong)] ring-1 ring-[var(--dock-item-ring)]',
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user