use custom scrollbar on all pages - avoids layout shifting on blog pages

This commit is contained in:
Michael Freno
2026-01-04 15:12:22 -05:00
parent a620a9f4c5
commit 2093859654
3 changed files with 191 additions and 195 deletions

View File

@@ -209,7 +209,6 @@
transition-duration: 500ms;
syntax: "*";
inherits: false;
scrollbar-color: var(--color-text) var(--color-base);
}
::selection {
@@ -224,14 +223,6 @@
background-color: var(--color-text);
color: var(--color-crust);
}
#center-body {
scrollbar-width: thin;
scrollbar-gutter: stable both-edges;
}
#center-body:has(.blog-overide) {
scrollbar-width: none !important;
scrollbar-gutter: unset !important;
}
#rightbar-content {
scrollbar-width: none;
}

View File

@@ -16,6 +16,7 @@ import { BarsProvider, useBars } from "./context/bars";
import { DarkModeProvider } from "./context/darkMode";
import { createWindowWidth, isMobile } from "~/lib/resize-utils";
import { MOBILE_CONFIG } from "./config";
import CustomScrollbar from "./components/CustomScrollbar";
function AppLayout(props: { children: any }) {
const {
@@ -167,7 +168,13 @@ function AppLayout(props: { children: any }) {
onTouchEnd={handleCenterTapRelease}
>
<Suspense fallback={<TerminalSplash />}>
{props.children}
<CustomScrollbar
autoHide={true}
autoHideDelay={1500}
rightOffset={250}
>
{props.children}
</CustomScrollbar>
</Suspense>
</div>
</ErrorBoundary>

View File

@@ -286,199 +286,197 @@ export default function PostPage() {
};
return (
<CustomScrollbar autoHide={true} autoHideDelay={1500} rightOffset={250}>
<Show
when={data()}
fallback={
<div class="flex h-screen items-center justify-center">
<Spinner size="xl" />
</div>
<Show
when={data()}
fallback={
<div class="flex h-screen items-center justify-center">
<Spinner size="xl" />
</div>
}
>
{(loadedData) => {
if ("redirect" in loadedData()) {
return <Navigate href={(loadedData() as any).redirect} />;
}
>
{(loadedData) => {
if ("redirect" in loadedData()) {
return <Navigate href={(loadedData() as any).redirect} />;
}
return (
<Show
when={loadedData().post as Post}
fallback={<Navigate href="/404" />}
>
{(p) => {
const postData = loadedData();
return (
<Show
when={loadedData().post as Post}
fallback={<Navigate href="/404" />}
>
{(p) => {
const postData = loadedData();
const userCommentMap = new Map<UserPublicData, number[]>(
postData.userCommentArray || []
);
const reactionMap = new Map<number, CommentReaction[]>(
postData.reactionArray || []
);
const userCommentMap = new Map<UserPublicData, number[]>(
postData.userCommentArray || []
);
const reactionMap = new Map<number, CommentReaction[]>(
postData.reactionArray || []
);
return (
<>
<Title>
{p().title.replaceAll("_", " ")} | Michael Freno
</Title>
<Meta
name="description"
content={
p().subtitle ||
`Read ${p().title.replaceAll("_", " ")} by Michael Freno on the freno.me blog.`
}
/>
return (
<>
<Title>
{p().title.replaceAll("_", " ")} | Michael Freno
</Title>
<Meta
name="description"
content={
p().subtitle ||
`Read ${p().title.replaceAll("_", " ")} by Michael Freno on the freno.me blog.`
}
/>
<div class="blog-overide relative -mt-16 overflow-x-hidden">
<div class="fixed inset-0 top-0 left-0 z-0 aspect-auto max-h-3/4 w-full overflow-hidden brightness-75 md:ml-62.5 md:max-h-[50vh] md:w-[calc(100vw-500px)]">
<img
src={p().banner_photo || "/blueprint.jpg"}
alt="post-cover"
class="h-full w-full object-cover select-none"
style={{
"pointer-events": "none"
}}
/>
<div class="fixed top-24 z-50 m-auto w-full px-4 text-center tracking-widest text-white backdrop-blur-md select-text text-shadow-lg backdrop:brightness-50 sm:top-36 md:top-[20vh] md:w-[calc(100vw-500px)]">
<div class="py-8 text-3xl font-semibold tracking-widest">
{p().title.replaceAll("_", " ")}
<Show when={p().subtitle}>
<div class="py-8 text-xl font-light tracking-widest">
{p().subtitle}
</div>
</Show>
</div>
</div>
</div>
<div class="z-10 pt-80 backdrop-blur-[0.01px] sm:pt-96 md:pt-[50vh]">
<div class="bg-base relative pb-24">
<div class="flex w-full flex-col justify-center pt-8 lg:flex-row lg:items-start lg:justify-between">
<div class="flex flex-col gap-2 px-4 md:px-8">
<div class="flex flex-col text-center md:text-left">
<div class="text-sm italic">
Written {new Date(p().date).toDateString()}
</div>
<Show when={p().last_edited_date !== p().date}>
<div class="text-subtext0 text-xs italic">
Edited:{" "}
{new Date(
p().last_edited_date
).toDateString()}
</div>
</Show>
</div>
<div class="text-center text-sm md:text-left">
By Michael Freno
</div>
<div class="flex flex-wrap justify-center gap-2 pt-2 md:justify-start">
<For each={postData.tags as any[]}>
{(tag) => {
const tagValue = tag.value;
return tagValue ? (
<A
href={`/blog?include=${encodeURIComponent(tagValue.split("#")[1])}`}
class="bg-teal rounded-lg px-3 py-1.5 text-xs font-medium text-white transition-all duration-200 hover:brightness-110 active:scale-95 sm:text-sm"
>
{tagValue}
</A>
) : null;
}}
</For>
</div>
<div class="blog-overide relative -mt-16 overflow-x-hidden">
<div class="fixed inset-0 top-0 left-0 z-0 aspect-auto max-h-3/4 w-full overflow-hidden brightness-75 md:ml-62.5 md:max-h-[50vh] md:w-[calc(100vw-500px)]">
<img
src={p().banner_photo || "/blueprint.jpg"}
alt="post-cover"
class="h-full w-full object-cover select-none"
style={{
"pointer-events": "none"
}}
/>
<div class="fixed top-24 z-50 m-auto w-full px-4 text-center tracking-widest text-white backdrop-blur-md select-text text-shadow-lg backdrop:brightness-50 sm:top-36 md:top-[20vh] md:w-[calc(100vw-500px)]">
<div class="py-8 text-3xl font-semibold tracking-widest">
{p().title.replaceAll("_", " ")}
<Show when={p().subtitle}>
<div class="py-8 text-xl font-light tracking-widest">
{p().subtitle}
</div>
<div class="flex flex-row justify-center gap-4 pt-6 lg:pt-0 lg:pr-8">
<div class="tooltip flex flex-col items-center">
<div>
<Fire
height={32}
width={32}
color="var(--color-red)"
/>
</div>
<div class="text-text pt-0.5 text-sm whitespace-nowrap">
{postData.reads || 0}{" "}
{postData.reads === 1 ? "Hit" : "Hits"}
</div>
</div>
<a href="#comments">
<button
onClick={() => {
document
.getElementById("comments")
?.scrollIntoView({ behavior: "smooth" });
}}
class="tooltip flex flex-col items-center"
>
<div class="hover:brightness-125">
<CommentIcon
strokeWidth={1}
height={32}
width={32}
/>
</div>
<div class="text-text pt-0.5 text-sm whitespace-nowrap">
{postData.comments.length}{" "}
{postData.comments.length === 1
? "Comment"
: "Comments"}
</div>
</button>
</a>
<div>
<SessionDependantLike
currentUserID={postData.userID}
privilegeLevel={postData.privilegeLevel}
likes={postData.likes as any[]}
projectID={p().id}
/>
</div>
</div>
</div>
<div class="py-8 text-center text-2xl font-semibold tracking-widest">
{p().title.replaceAll("_", " ")}
<Show when={p().subtitle}>
<div class="py-8 text-xl font-light tracking-widest">
{p().subtitle}
</div>
</Show>
</div>
<PostBodyClient
body={p().body}
hasCodeBlock={hasCodeBlock(p().body)}
/>
<div
id="comments"
class="mx-4 pt-12 pb-12 md:mx-8 lg:mx-12"
>
<CommentSectionWrapper
privilegeLevel={postData.privilegeLevel}
allComments={postData.comments as Comment[]}
topLevelComments={
postData.topLevelComments as Comment[]
}
id={p().id}
reactionMap={reactionMap}
currentUserID={postData.userID || ""}
userCommentMap={userCommentMap}
/>
</div>
</Show>
</div>
</div>
</div>
</>
);
}}
</Show>
);
}}
</Show>
</CustomScrollbar>
<div class="z-10 pt-80 backdrop-blur-[0.01px] sm:pt-96 md:pt-[50vh]">
<div class="bg-base relative pb-24">
<div class="flex w-full flex-col justify-center pt-8 lg:flex-row lg:items-start lg:justify-between">
<div class="flex flex-col gap-2 px-4 md:px-8">
<div class="flex flex-col text-center md:text-left">
<div class="text-sm italic">
Written {new Date(p().date).toDateString()}
</div>
<Show when={p().last_edited_date !== p().date}>
<div class="text-subtext0 text-xs italic">
Edited:{" "}
{new Date(
p().last_edited_date
).toDateString()}
</div>
</Show>
</div>
<div class="text-center text-sm md:text-left">
By Michael Freno
</div>
<div class="flex flex-wrap justify-center gap-2 pt-2 md:justify-start">
<For each={postData.tags as any[]}>
{(tag) => {
const tagValue = tag.value;
return tagValue ? (
<A
href={`/blog?include=${encodeURIComponent(tagValue.split("#")[1])}`}
class="bg-teal rounded-lg px-3 py-1.5 text-xs font-medium text-white transition-all duration-200 hover:brightness-110 active:scale-95 sm:text-sm"
>
{tagValue}
</A>
) : null;
}}
</For>
</div>
</div>
<div class="flex flex-row justify-center gap-4 pt-6 lg:pt-0 lg:pr-8">
<div class="tooltip flex flex-col items-center">
<div>
<Fire
height={32}
width={32}
color="var(--color-red)"
/>
</div>
<div class="text-text pt-0.5 text-sm whitespace-nowrap">
{postData.reads || 0}{" "}
{postData.reads === 1 ? "Hit" : "Hits"}
</div>
</div>
<a href="#comments">
<button
onClick={() => {
document
.getElementById("comments")
?.scrollIntoView({ behavior: "smooth" });
}}
class="tooltip flex flex-col items-center"
>
<div class="hover:brightness-125">
<CommentIcon
strokeWidth={1}
height={32}
width={32}
/>
</div>
<div class="text-text pt-0.5 text-sm whitespace-nowrap">
{postData.comments.length}{" "}
{postData.comments.length === 1
? "Comment"
: "Comments"}
</div>
</button>
</a>
<div>
<SessionDependantLike
currentUserID={postData.userID}
privilegeLevel={postData.privilegeLevel}
likes={postData.likes as any[]}
projectID={p().id}
/>
</div>
</div>
</div>
<div class="py-8 text-center text-2xl font-semibold tracking-widest">
{p().title.replaceAll("_", " ")}
<Show when={p().subtitle}>
<div class="py-8 text-xl font-light tracking-widest">
{p().subtitle}
</div>
</Show>
</div>
<PostBodyClient
body={p().body}
hasCodeBlock={hasCodeBlock(p().body)}
/>
<div
id="comments"
class="mx-4 pt-12 pb-12 md:mx-8 lg:mx-12"
>
<CommentSectionWrapper
privilegeLevel={postData.privilegeLevel}
allComments={postData.comments as Comment[]}
topLevelComments={
postData.topLevelComments as Comment[]
}
id={p().id}
reactionMap={reactionMap}
currentUserID={postData.userID || ""}
userCommentMap={userCommentMap}
/>
</div>
</div>
</div>
</div>
</>
);
}}
</Show>
);
}}
</Show>
);
}