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

View File

@ -1,37 +1,36 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/assets/favicon.svg" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
/>
<head>
<meta charset="utf-8" />
<link rel="icon" href="/assets/favicon.svg" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
/>
<meta
name="description"
content="Airbnb co-hosting and property management."
/>
<link rel="apple-touch-icon" href="/assets/favicon192.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap"
rel="stylesheet"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<link rel="stylesheet" href="/global.css" />
<link rel="stylesheet" href="/fonts.css" />
<link rel="manifest" href="/manifest.json" />
<title>The Hideout</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="th-root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
<meta
name="description"
content="Airbnb co-hosting and property management."
/>
<link rel="apple-touch-icon" href="/assets/favicon192.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Libre+Baskerville:ital,wght@0,400;0,700;1,400&display=swap"
rel="stylesheet"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<link rel="stylesheet" href="/fonts.css" />
<link rel="manifest" href="/manifest.json" />
<title>The Hideout</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="th-root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>

View File

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

View File

@ -19,6 +19,7 @@ import {
useSettingsContext,
} from "./contexts/SettingsContext";
const apiUrl = import.meta.env.VITE_API_URL;
import "./global.css";
// Component that handles image loading after API data is fetched
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 { Layout } from "antd";
import ContentRenderer from "./ContentRenderer";
import HeaderLogo from "./HeaderLogo";
import ScrollIcon from "../icons/ScrollIcon";
@ -9,8 +8,8 @@ import ImageCarousel from "./ImageCarousel";
import CloseButton from "./CloseButton";
import MenuButton from "./MenuButton";
import { useSettingsContext } from "../contexts/SettingsContext";
const { Content } = Layout;
import Simplebar from "simplebar-react";
import "simplebar-react/dist/simplebar.min.css";
const Page = ({
pageData,
@ -29,10 +28,11 @@ const Page = ({
!pageData.showProperties &&
!pageData.showContactForm &&
!pageData.hideMobileImage;
const contentRef = useRef(null);
const scrollRef = useRef(null);
const [isImageShrunk, setIsImageShrunk] = useState(false);
const [safariBlurToggle, setSafariBlurToggle] = useState(false);
const shrinkDistance = 1;
const [hasVerticalScrollbar, setHasVerticalScrollbar] = useState(false);
const settings = useSettingsContext();
const themes = settings?.themes || [];
@ -43,8 +43,9 @@ const Page = ({
// Reset scroll position and image state when visible becomes false
useEffect(() => {
if (!visible && contentRef.current) {
contentRef.current.scrollTop = 0;
if (!visible && scrollRef.current) {
const el = scrollRef.current.getScrollElement();
el.scrollTop = 0;
setIsImageShrunk(false);
}
}, [visible]);
@ -83,14 +84,50 @@ const Page = ({
};
}, [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(() => {
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 = () => {
// Don't shrink image if page is not visible
if (!visible) return;
const el = contentRef.current;
const el = scrollRef.current.getScrollElement();
const scrollTop = el.scrollTop;
const scrollHeight = el.scrollHeight;
const clientHeight = el.clientHeight;
@ -118,7 +155,7 @@ const Page = ({
}
};
const el = contentRef.current;
const el = scrollRef.current.getScrollElement();
el.addEventListener("scroll", handleScroll);
return () => {
@ -172,22 +209,34 @@ const Page = ({
<ImageCarousel page={pageData} className="th-mobile-image" />
</div>
)}
<Content
<div
className={`th-page-content${
visible == false ? " th-page-content-hidden" : ""
}${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 ${
mobileImage ? " th-content-container-mobile-image" : ""
className={`th-content-container-wrapper${
hasVerticalScrollbar ? " th-content-container-wrapper-scroll" : ""
}`}
>
<ContentRenderer content={pageData?.content} />
<div
className={`th-content-container ${
mobileImage ? " th-content-container-mobile-image" : ""
}`}
>
<ContentRenderer content={pageData?.content} />
</div>
</div>
</div>
</Content>
</Simplebar>
</div>
{pageData?.showScroll == true && <ScrollIcon visible={visible} />}
</div>
);

View File

@ -303,7 +303,8 @@ hr.th-divider {
flex-grow: 3;
padding: 0 var(--th-page-padding);
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%;
color: var(--th-textColor);
display: flex;
@ -311,9 +312,36 @@ hr.th-divider {
align-items: start;
text-align: left;
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 {
@ -450,6 +478,10 @@ hr.th-divider {
flex-grow: 1;
}
.th-content-container-wrapper-scroll {
padding-right: 20px;
}
/* App component styles */
.th-app-container {
height: var(--unit-100vh);
@ -755,8 +787,10 @@ hr.th-divider {
overflow: hidden; /* Prevent overflow */
border-bottom: 1px solid var(--th-textColor);
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;
min-height: 260px;
max-height: 260px;
}
@ -765,6 +799,7 @@ hr.th-divider {
}
.th-page-mobile-image-wrapper-shrunk {
min-height: 110px;
max-height: 110px;
}

View File

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

935
yarn.lock

File diff suppressed because it is too large Load Diff