Compare commits

...

3 Commits

Author SHA1 Message Date
9561887e4b Added simplebar support 2025-11-16 13:29:04 +00:00
040d7133f6 Make CSS no longer import via HTML 2025-11-16 13:28:38 +00:00
f1c57dde3b Fixed dev envs 2025-11-16 13:28:22 +00:00
8 changed files with 704 additions and 434 deletions

View File

@ -1,2 +1,2 @@
VITE_API_URL=https://thehideout.tombutcher.work/api VITE_API_URL=https://dev.tombutcher.work/api
VITE_TURNSTILE_KEY=0x4AAAAAAB2dBq6i8m4kYzDm VITE_TURNSTILE_KEY=0x4AAAAAAB2dBq6i8m4kYzDm

View File

@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@ -24,7 +24,6 @@
name="apple-mobile-web-app-status-bar-style" name="apple-mobile-web-app-status-bar-style"
content="black-translucent" content="black-translucent"
/> />
<link rel="stylesheet" href="/global.css" />
<link rel="stylesheet" href="/fonts.css" /> <link rel="stylesheet" href="/fonts.css" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<title>The Hideout</title> <title>The Hideout</title>

View File

@ -29,6 +29,7 @@
"react-scroll": "^1.9.3", "react-scroll": "^1.9.3",
"react-turnstile": "^1.1.4", "react-turnstile": "^1.1.4",
"sass": "^1.86.3", "sass": "^1.86.3",
"simplebar-react": "^3.3.2",
"vite": "^6.2.5", "vite": "^6.2.5",
"vite-plugin-svgo": "^2.0.0", "vite-plugin-svgo": "^2.0.0",
"vite-plugin-svgr": "^4.5.0", "vite-plugin-svgr": "^4.5.0",

View File

@ -19,6 +19,7 @@ import {
useSettingsContext, useSettingsContext,
} from "./contexts/SettingsContext"; } from "./contexts/SettingsContext";
const apiUrl = import.meta.env.VITE_API_URL; const apiUrl = import.meta.env.VITE_API_URL;
import "./global.css";
// Component that handles image loading after API data is fetched // Component that handles image loading after API data is fetched
const AppContent = ({ pages, properties, images }) => { const AppContent = ({ pages, properties, images }) => {

View File

@ -1,6 +1,5 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Layout } from "antd";
import ContentRenderer from "./ContentRenderer"; import ContentRenderer from "./ContentRenderer";
import HeaderLogo from "./HeaderLogo"; import HeaderLogo from "./HeaderLogo";
import ScrollIcon from "../icons/ScrollIcon"; import ScrollIcon from "../icons/ScrollIcon";
@ -9,8 +8,8 @@ import ImageCarousel from "./ImageCarousel";
import CloseButton from "./CloseButton"; import CloseButton from "./CloseButton";
import MenuButton from "./MenuButton"; import MenuButton from "./MenuButton";
import { useSettingsContext } from "../contexts/SettingsContext"; import { useSettingsContext } from "../contexts/SettingsContext";
import Simplebar from "simplebar-react";
const { Content } = Layout; import "simplebar-react/dist/simplebar.min.css";
const Page = ({ const Page = ({
pageData, pageData,
@ -29,10 +28,11 @@ const Page = ({
!pageData.showProperties && !pageData.showProperties &&
!pageData.showContactForm && !pageData.showContactForm &&
!pageData.hideMobileImage; !pageData.hideMobileImage;
const contentRef = useRef(null); const scrollRef = useRef(null);
const [isImageShrunk, setIsImageShrunk] = useState(false); const [isImageShrunk, setIsImageShrunk] = useState(false);
const [safariBlurToggle, setSafariBlurToggle] = useState(false); const [safariBlurToggle, setSafariBlurToggle] = useState(false);
const shrinkDistance = 1; const shrinkDistance = 1;
const [hasVerticalScrollbar, setHasVerticalScrollbar] = useState(false);
const settings = useSettingsContext(); const settings = useSettingsContext();
const themes = settings?.themes || []; const themes = settings?.themes || [];
@ -43,8 +43,9 @@ const Page = ({
// Reset scroll position and image state when visible becomes false // Reset scroll position and image state when visible becomes false
useEffect(() => { useEffect(() => {
if (!visible && contentRef.current) { if (!visible && scrollRef.current) {
contentRef.current.scrollTop = 0; const el = scrollRef.current.getScrollElement();
el.scrollTop = 0;
setIsImageShrunk(false); setIsImageShrunk(false);
} }
}, [visible]); }, [visible]);
@ -83,14 +84,50 @@ const Page = ({
}; };
}, [isImageShrunk, mobileImage]); }, [isImageShrunk, mobileImage]);
const checkOverflow = useCallback(() => {
if (!scrollRef.current) return;
const el = scrollRef.current.getScrollElement();
if (!el) return;
const canScroll = el.scrollHeight > el.clientHeight;
setHasVerticalScrollbar(canScroll);
}, []);
// Recalculate overflow on mount, when layout/content changes, and on resize
useEffect(() => { useEffect(() => {
if (!contentRef.current || !mobileImage) return; checkOverflow();
}, [
checkOverflow,
pageData?.content,
visible,
isLargeMobile,
isMobile,
mobileImage,
]);
// Observe size changes of the scroll container
useEffect(() => {
if (!scrollRef.current) return;
const el = scrollRef.current.getScrollElement();
if (!el) return;
const resizeObserver = new ResizeObserver(() => {
checkOverflow();
});
resizeObserver.observe(el);
window.addEventListener("resize", checkOverflow);
return () => {
resizeObserver.disconnect();
window.removeEventListener("resize", checkOverflow);
};
}, [checkOverflow]);
useEffect(() => {
if (!scrollRef.current || !mobileImage) return;
const handleScroll = () => { const handleScroll = () => {
// Don't shrink image if page is not visible // Don't shrink image if page is not visible
if (!visible) return; if (!visible) return;
const el = contentRef.current; const el = scrollRef.current.getScrollElement();
const scrollTop = el.scrollTop; const scrollTop = el.scrollTop;
const scrollHeight = el.scrollHeight; const scrollHeight = el.scrollHeight;
const clientHeight = el.clientHeight; const clientHeight = el.clientHeight;
@ -118,7 +155,7 @@ const Page = ({
} }
}; };
const el = contentRef.current; const el = scrollRef.current.getScrollElement();
el.addEventListener("scroll", handleScroll); el.addEventListener("scroll", handleScroll);
return () => { return () => {
@ -172,13 +209,23 @@ const Page = ({
<ImageCarousel page={pageData} className="th-mobile-image" /> <ImageCarousel page={pageData} className="th-mobile-image" />
</div> </div>
)} )}
<Content
<div
className={`th-page-content${ className={`th-page-content${
visible == false ? " th-page-content-hidden" : "" visible == false ? " th-page-content-hidden" : ""
}${isLargeMobile ? " th-page-content-mobile" : ""}`} }${isLargeMobile ? " th-page-content-mobile" : ""}`}
ref={contentRef}
> >
<div className="th-content-container-wrapper"> <Simplebar
className="th-content-scroll"
ref={scrollRef}
forceVisible="y"
autoHide={false}
>
<div
className={`th-content-container-wrapper${
hasVerticalScrollbar ? " th-content-container-wrapper-scroll" : ""
}`}
>
<div <div
className={`th-content-container ${ className={`th-content-container ${
mobileImage ? " th-content-container-mobile-image" : "" mobileImage ? " th-content-container-mobile-image" : ""
@ -187,7 +234,9 @@ const Page = ({
<ContentRenderer content={pageData?.content} /> <ContentRenderer content={pageData?.content} />
</div> </div>
</div> </div>
</Content> </Simplebar>
</div>
{pageData?.showScroll == true && <ScrollIcon visible={visible} />} {pageData?.showScroll == true && <ScrollIcon visible={visible} />}
</div> </div>
); );

View File

@ -303,7 +303,8 @@ hr.th-divider {
flex-grow: 3; flex-grow: 3;
padding: 0 var(--th-page-padding); padding: 0 var(--th-page-padding);
margin: 0; margin: 0;
height: var(--unit-100vh); /* Use fixed height instead of min-height */ height: 100%; /* Use fixed height instead of min-height */
min-height: 0;
width: 100%; width: 100%;
color: var(--th-textColor); color: var(--th-textColor);
display: flex; display: flex;
@ -311,9 +312,36 @@ hr.th-divider {
align-items: start; align-items: start;
text-align: left; text-align: left;
transition: opacity 0.3s ease-in-out; transition: opacity 0.3s ease-in-out;
/* Prevent layout shifts */ }
contain: layout style;
overflow-y: scroll; .th-content-scroll {
height: 100%;
width: 100%;
flex: 1 1 auto;
}
.simplebar-scrollbar:before {
background: var(--th-textColor);
}
.simplebar-track.simplebar-vertical {
width: 6px;
}
.simplebar-scrollbar.simplebar-visible:before {
opacity: 1;
}
/* Ensure Simplebar fills and scrolls correctly */
.th-content-scroll .simplebar-content-wrapper {
height: 100%;
overflow-y: auto;
}
.th-content-scroll .simplebar-content {
min-height: 100%;
display: flex;
flex-direction: column;
} }
.th-page-content-mobile { .th-page-content-mobile {
@ -450,6 +478,10 @@ hr.th-divider {
flex-grow: 1; flex-grow: 1;
} }
.th-content-container-wrapper-scroll {
padding-right: 20px;
}
/* App component styles */ /* App component styles */
.th-app-container { .th-app-container {
height: var(--unit-100vh); height: var(--unit-100vh);
@ -755,8 +787,10 @@ hr.th-divider {
overflow: hidden; /* Prevent overflow */ overflow: hidden; /* Prevent overflow */
border-bottom: 1px solid var(--th-textColor); border-bottom: 1px solid var(--th-textColor);
transition: opacity 0.3s ease-in-out, transition: opacity 0.3s ease-in-out,
max-height 0.75s cubic-bezier(0, 0.5, 0.5, 1); max-height 0.75s cubic-bezier(0, 0.5, 0.5, 1),
min-height 0.75s cubic-bezier(0, 0.5, 0.5, 1);
opacity: 1; opacity: 1;
min-height: 260px;
max-height: 260px; max-height: 260px;
} }
@ -765,6 +799,7 @@ hr.th-divider {
} }
.th-page-mobile-image-wrapper-shrunk { .th-page-mobile-image-wrapper-shrunk {
min-height: 110px;
max-height: 110px; max-height: 110px;
} }

View File

@ -8,7 +8,7 @@ export default defineConfig({
plugins: [react(), svgo(), svgr()], plugins: [react(), svgo(), svgr()],
server: { server: {
host: "0.0.0.0", host: "0.0.0.0",
allowedHosts: ["thehideout.tombutcher.work"], allowedHosts: ["dev.tombutcher.work"],
}, },
base: "/", base: "/",
}); });

935
yarn.lock

File diff suppressed because it is too large Load Diff