refactor: whitespace fixes
This commit is contained in:
+80
-77
@@ -1,6 +1,6 @@
|
|||||||
import { lazy, Suspense, useEffect, useRef } from "react";
|
import { lazy, Suspense, useEffect, useRef } from "react";
|
||||||
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
|
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
|
||||||
import { ProtectedRoute, AutoRedirectRoute } from "./components/RouteGuards";
|
import { AutoRedirectRoute, ProtectedRoute } from "./components/RouteGuards";
|
||||||
import SplashScreen from "./components/SplashScreen";
|
import SplashScreen from "./components/SplashScreen";
|
||||||
import { ROUTES } from "./config/routes";
|
import { ROUTES } from "./config/routes";
|
||||||
import { useAuth } from "./hooks/useAuth";
|
import { useAuth } from "./hooks/useAuth";
|
||||||
@@ -16,85 +16,88 @@ const VerifyEmail = lazy(() => import("./pages/VerifyEmail"));
|
|||||||
const About = lazy(() => import("./pages/About"));
|
const About = lazy(() => import("./pages/About"));
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { initialize, isInitializing } = useAuth();
|
const { initialize, isInitializing } = useAuth();
|
||||||
const authInitialized = useRef<boolean>(false);
|
const authInitialized = useRef<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (authInitialized.current) return;
|
if (authInitialized.current) return;
|
||||||
authInitialized.current = true;
|
authInitialized.current = true;
|
||||||
initialize().then();
|
initialize().then();
|
||||||
}, [initialize]);
|
}, [initialize]);
|
||||||
|
|
||||||
if (isInitializing) {
|
if (isInitializing) {
|
||||||
return <SplashScreen />;
|
return <SplashScreen />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<main className="relative min-h-screen min-w-screen flex items-center justify-center w-full bg-base-200 before:absolute before:top-0 before:left-0 before:w-full before:h-full before:content-[''] before:opacity-[0.03] before:z-50 before:pointer-events-none before:bg-[url('assets/noise.gif')]">
|
<main className="relative min-h-screen min-w-screen flex items-center justify-center w-full bg-base-200 before:absolute before:top-0 before:left-0 before:w-full before:h-full before:content-[''] before:opacity-[0.03] before:z-50 before:pointer-events-none before:bg-[url('assets/noise.gif')]">
|
||||||
<Suspense fallback={<SplashScreen />}>
|
<Suspense fallback={<SplashScreen />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={ROUTES.HOME} element={
|
<Route
|
||||||
<AutoRedirectRoute>
|
path={ROUTES.HOME}
|
||||||
<Home />
|
element={
|
||||||
</AutoRedirectRoute>
|
<AutoRedirectRoute>
|
||||||
} />
|
<Home />
|
||||||
|
</AutoRedirectRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.ONBOARD}
|
path={ROUTES.ONBOARD}
|
||||||
element={
|
element={
|
||||||
<AutoRedirectRoute>
|
<AutoRedirectRoute>
|
||||||
<Register />
|
<Register />
|
||||||
</AutoRedirectRoute>
|
</AutoRedirectRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.LOGIN}
|
path={ROUTES.LOGIN}
|
||||||
element={
|
element={
|
||||||
<AutoRedirectRoute>
|
<AutoRedirectRoute>
|
||||||
<Login />
|
<Login />
|
||||||
</AutoRedirectRoute>
|
</AutoRedirectRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.VERIFY_EMAIL}
|
path={ROUTES.VERIFY_EMAIL}
|
||||||
element={
|
element={
|
||||||
<AutoRedirectRoute>
|
<AutoRedirectRoute>
|
||||||
<VerifyEmail />
|
<VerifyEmail />
|
||||||
</AutoRedirectRoute>
|
</AutoRedirectRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.ACTIVATE}
|
path={ROUTES.ACTIVATE}
|
||||||
element={
|
element={
|
||||||
<AutoRedirectRoute>
|
<AutoRedirectRoute>
|
||||||
<Activate />
|
<Activate />
|
||||||
</AutoRedirectRoute>
|
</AutoRedirectRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.DRAWER}
|
path={ROUTES.DRAWER}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<Drawer />
|
<Drawer />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.WRITE}
|
path={ROUTES.WRITE}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<Editor />
|
<Editor />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path={ROUTES.READ} element={<Reader />} />
|
<Route path={ROUTES.READ} element={<Reader />} />
|
||||||
<Route path={ROUTES.ABOUT} element={<About />} />
|
<Route path={ROUTES.ABOUT} element={<About />} />
|
||||||
<Route path="*" element={<Navigate to={ROUTES.HOME} replace />} />
|
<Route path="*" element={<Navigate to={ROUTES.HOME} replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</main>
|
</main>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,54 +2,63 @@ import { DotIcon } from "@phosphor-icons/react";
|
|||||||
import "@fontsource/knewave/400.css";
|
import "@fontsource/knewave/400.css";
|
||||||
|
|
||||||
interface LogoProps {
|
interface LogoProps {
|
||||||
scale?: number;
|
scale?: number;
|
||||||
type?: "inline" | "mono" | "logo" | null;
|
type?: "inline" | "mono" | "logo" | null;
|
||||||
ul?: boolean;
|
ul?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Logo({ scale = 1, type = null, ul = false }: LogoProps) {
|
export default function Logo({
|
||||||
if (type === "inline") {
|
scale = 1,
|
||||||
return (
|
type = null,
|
||||||
<span className={"text-accent font-display italic "}>
|
ul = false,
|
||||||
pi<span className="text-primary">.</span> ku
|
}: LogoProps) {
|
||||||
<span className="text-primary">.</span>
|
if (type === "inline") {
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "mono") {
|
|
||||||
return (
|
|
||||||
<span className="font-display italic font-bold border-b-3 border-dashed border-stone-800/50">
|
|
||||||
pi. ku.
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === "logo") {
|
|
||||||
return (
|
|
||||||
<img src="/android-chrome-512x512.png" alt="Pi. Ku. logo" className="mx-auto" width={scale * 100} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<span className={"text-accent font-display italic "}>
|
||||||
role="img"
|
pi<span className="text-primary">.</span> ku
|
||||||
aria-label="Pi. Ku. logo"
|
<span className="text-primary">.</span>
|
||||||
className={`inline-flex items-baseline justify-center leading-none select-none ${ul ? "ul-wavy" : ""}`}
|
</span>
|
||||||
style={{ fontFamily: "'Knewave', serif", scale }}
|
|
||||||
>
|
|
||||||
<span className="text-3xl font-light text-accent">Pi</span>
|
|
||||||
<DotIcon
|
|
||||||
weight="fill"
|
|
||||||
size={12}
|
|
||||||
className="text-primary translate-y-1 -mx-px"
|
|
||||||
/>
|
|
||||||
<span className="text-3xl font-light text-accent"> Ku</span>
|
|
||||||
<DotIcon
|
|
||||||
weight="fill"
|
|
||||||
size={12}
|
|
||||||
className="text-primary translate-y-1 -mx-px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "mono") {
|
||||||
|
return (
|
||||||
|
<span className="font-display italic font-bold border-b-3 border-dashed border-stone-800/50">
|
||||||
|
pi. ku.
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "logo") {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
src="/android-chrome-512x512.png"
|
||||||
|
alt="Pi. Ku. logo"
|
||||||
|
className="mx-auto"
|
||||||
|
width={scale * 100}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="img"
|
||||||
|
aria-label="Pi. Ku. logo"
|
||||||
|
className={`inline-flex items-baseline justify-center leading-none select-none ${ul ? "ul-wavy" : ""}`}
|
||||||
|
style={{ fontFamily: "'Knewave', serif", scale }}
|
||||||
|
>
|
||||||
|
<span className="text-3xl font-light text-accent">Pi</span>
|
||||||
|
<DotIcon
|
||||||
|
weight="fill"
|
||||||
|
size={12}
|
||||||
|
className="text-primary translate-y-1 -mx-px"
|
||||||
|
/>
|
||||||
|
<span className="text-3xl font-light text-accent"> Ku</span>
|
||||||
|
<DotIcon
|
||||||
|
weight="fill"
|
||||||
|
size={12}
|
||||||
|
className="text-primary translate-y-1 -mx-px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,125 +3,125 @@ import { MemoryRouter, Route, Routes } from "react-router-dom";
|
|||||||
import { beforeEach, describe, expect, it } from "vitest";
|
import { beforeEach, describe, expect, it } from "vitest";
|
||||||
import { mockUser } from "../../test/fixtures/user.fixture";
|
import { mockUser } from "../../test/fixtures/user.fixture";
|
||||||
import { useAuthStore } from "../store/useAuthStore";
|
import { useAuthStore } from "../store/useAuthStore";
|
||||||
import { ProtectedRoute, AutoRedirectRoute } from "./RouteGuards";
|
import { AutoRedirectRoute, ProtectedRoute } from "./RouteGuards";
|
||||||
|
|
||||||
function renderGuard(ui: React.ReactNode, mountPath: "/protected" | "/public") {
|
function renderGuard(ui: React.ReactNode, mountPath: "/protected" | "/public") {
|
||||||
return render(
|
return render(
|
||||||
<MemoryRouter initialEntries={[mountPath]}>
|
<MemoryRouter initialEntries={[mountPath]}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<div>Login Page</div>} />
|
<Route path="/login" element={<div>Login Page</div>} />
|
||||||
<Route path="/drawer" element={<div>Drawer Page</div>} />
|
<Route path="/drawer" element={<div>Drawer Page</div>} />
|
||||||
<Route path="/protected" element={ui} />
|
<Route path="/protected" element={ui} />
|
||||||
<Route path="/public" element={ui} />
|
<Route path="/public" element={ui} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
user: null,
|
user: null,
|
||||||
isInitializing: true,
|
isInitializing: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ProtectedRoute", () => {
|
describe("ProtectedRoute", () => {
|
||||||
it("should show SplashScreen while auth is initializing", () => {
|
it("should show SplashScreen while auth is initializing", () => {
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
isInitializing: true,
|
isInitializing: true,
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
user: null,
|
user: null,
|
||||||
});
|
|
||||||
renderGuard(
|
|
||||||
<ProtectedRoute>
|
|
||||||
<div>Secret</div>
|
|
||||||
</ProtectedRoute>,
|
|
||||||
"/protected",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText(/Unsealing/i)).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText("Secret")).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
renderGuard(
|
||||||
|
<ProtectedRoute>
|
||||||
|
<div>Secret</div>
|
||||||
|
</ProtectedRoute>,
|
||||||
|
"/protected",
|
||||||
|
);
|
||||||
|
|
||||||
it("should redirect unauthenticated users to /login", () => {
|
expect(screen.getByText(/Unsealing/i)).toBeInTheDocument();
|
||||||
useAuthStore.setState({
|
expect(screen.queryByText("Secret")).not.toBeInTheDocument();
|
||||||
isInitializing: false,
|
});
|
||||||
accessToken: null,
|
|
||||||
user: null,
|
it("should redirect unauthenticated users to /login", () => {
|
||||||
});
|
useAuthStore.setState({
|
||||||
renderGuard(
|
isInitializing: false,
|
||||||
<ProtectedRoute>
|
accessToken: null,
|
||||||
<div>Secret</div>
|
user: null,
|
||||||
</ProtectedRoute>,
|
|
||||||
"/protected",
|
|
||||||
);
|
|
||||||
expect(screen.getByText("Login Page")).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText("Secret")).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
renderGuard(
|
||||||
|
<ProtectedRoute>
|
||||||
|
<div>Secret</div>
|
||||||
|
</ProtectedRoute>,
|
||||||
|
"/protected",
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Login Page")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("Secret")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("should render page for authenticated users", () => {
|
it("should render page for authenticated users", () => {
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
isInitializing: false,
|
isInitializing: false,
|
||||||
accessToken: "token",
|
accessToken: "token",
|
||||||
user: mockUser,
|
user: mockUser,
|
||||||
});
|
|
||||||
renderGuard(
|
|
||||||
<ProtectedRoute>
|
|
||||||
<div>Secret</div>
|
|
||||||
</ProtectedRoute>,
|
|
||||||
"/protected",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(screen.getByText("Secret")).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
renderGuard(
|
||||||
|
<ProtectedRoute>
|
||||||
|
<div>Secret</div>
|
||||||
|
</ProtectedRoute>,
|
||||||
|
"/protected",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("Secret")).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("PublicRoute", () => {
|
describe("PublicRoute", () => {
|
||||||
it("should show SplashScreen while auth is initializing", () => {
|
it("should show SplashScreen while auth is initializing", () => {
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
isInitializing: true,
|
isInitializing: true,
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
user: null,
|
user: null,
|
||||||
});
|
|
||||||
renderGuard(
|
|
||||||
<AutoRedirectRoute>
|
|
||||||
<div>Login Page</div>
|
|
||||||
</AutoRedirectRoute>,
|
|
||||||
"/public",
|
|
||||||
);
|
|
||||||
expect(screen.getByText(/Unsealing/i)).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText("Login Page")).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
renderGuard(
|
||||||
|
<AutoRedirectRoute>
|
||||||
|
<div>Login Page</div>
|
||||||
|
</AutoRedirectRoute>,
|
||||||
|
"/public",
|
||||||
|
);
|
||||||
|
expect(screen.getByText(/Unsealing/i)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("Login Page")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("should redirect authenticated users to /drawer", () => {
|
it("should redirect authenticated users to /drawer", () => {
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
isInitializing: false,
|
isInitializing: false,
|
||||||
accessToken: "token",
|
accessToken: "token",
|
||||||
user: mockUser,
|
user: mockUser,
|
||||||
});
|
|
||||||
renderGuard(
|
|
||||||
<AutoRedirectRoute>
|
|
||||||
<div>Login Form</div>
|
|
||||||
</AutoRedirectRoute>,
|
|
||||||
"/public",
|
|
||||||
);
|
|
||||||
expect(screen.getByText("Drawer Page")).toBeInTheDocument();
|
|
||||||
expect(screen.queryByText("Login Form")).not.toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
renderGuard(
|
||||||
|
<AutoRedirectRoute>
|
||||||
|
<div>Login Form</div>
|
||||||
|
</AutoRedirectRoute>,
|
||||||
|
"/public",
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Drawer Page")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("Login Form")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("should render page for unauthenticated users", () => {
|
it("should render page for unauthenticated users", () => {
|
||||||
useAuthStore.setState({
|
useAuthStore.setState({
|
||||||
isInitializing: false,
|
isInitializing: false,
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
user: null,
|
user: null,
|
||||||
});
|
|
||||||
renderGuard(
|
|
||||||
<AutoRedirectRoute>
|
|
||||||
<div>Login Form</div>
|
|
||||||
</AutoRedirectRoute>,
|
|
||||||
"/public",
|
|
||||||
);
|
|
||||||
expect(screen.getByText("Login Form")).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
renderGuard(
|
||||||
|
<AutoRedirectRoute>
|
||||||
|
<div>Login Form</div>
|
||||||
|
</AutoRedirectRoute>,
|
||||||
|
"/public",
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Login Form")).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,16 +9,16 @@ import SplashScreen from "./SplashScreen";
|
|||||||
* state so the Login component can link them back after sign-in
|
* state so the Login component can link them back after sign-in
|
||||||
*/
|
*/
|
||||||
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||||
const { isAuthenticated, isInitializing } = useAuth();
|
const { isAuthenticated, isInitializing } = useAuth();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
if (isInitializing) return <SplashScreen />;
|
if (isInitializing) return <SplashScreen />;
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
if (!isAuthenticated) {
|
||||||
return <Navigate to={ROUTES.LOGIN} state={{ from: location }} replace />;
|
return <Navigate to={ROUTES.LOGIN} state={{ from: location }} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,13 +26,13 @@ export function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
|||||||
* If authenticated, redirect all the auth related flows to the drawer
|
* If authenticated, redirect all the auth related flows to the drawer
|
||||||
*/
|
*/
|
||||||
export function AutoRedirectRoute({ children }: { children: React.ReactNode }) {
|
export function AutoRedirectRoute({ children }: { children: React.ReactNode }) {
|
||||||
const { isAuthenticated, isInitializing } = useAuth();
|
const { isAuthenticated, isInitializing } = useAuth();
|
||||||
|
|
||||||
if (isInitializing) return <SplashScreen />;
|
if (isInitializing) return <SplashScreen />;
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return <Navigate to={ROUTES.DRAWER} replace />;
|
return <Navigate to={ROUTES.DRAWER} replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|||||||
+376
-376
@@ -1,10 +1,10 @@
|
|||||||
import { InfoIcon } from "@phosphor-icons/react";
|
import { InfoIcon } from "@phosphor-icons/react";
|
||||||
import { ReactLenis } from "lenis/react";
|
import { ReactLenis } from "lenis/react";
|
||||||
import {
|
import {
|
||||||
motion,
|
motion,
|
||||||
useMotionValueEvent,
|
useMotionValueEvent,
|
||||||
useScroll,
|
useScroll,
|
||||||
useTransform,
|
useTransform,
|
||||||
} from "motion/react";
|
} from "motion/react";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@@ -18,383 +18,383 @@ import "@fontsource/space-mono/index.css";
|
|||||||
import "@fontsource/architects-daughter/index.css";
|
import "@fontsource/architects-daughter/index.css";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const sectionContainer1 = useRef<HTMLDivElement>(null);
|
const sectionContainer1 = useRef<HTMLDivElement>(null);
|
||||||
const { scrollYProgress } = useScroll({
|
const { scrollYProgress } = useScroll({
|
||||||
target: sectionContainer1,
|
target: sectionContainer1,
|
||||||
});
|
});
|
||||||
const [isEnvelopeFlipped, setIsEnvelopeFlipped] = useState(true);
|
const [isEnvelopeFlipped, setIsEnvelopeFlipped] = useState(true);
|
||||||
const [flapOpen, setFlapOpen] = useState(false);
|
const [flapOpen, setFlapOpen] = useState(false);
|
||||||
const [recipient, setRecipient] = useState("someone dear");
|
const [recipient, setRecipient] = useState("someone dear");
|
||||||
const [ignite, setIgnite] = useState(false);
|
const [ignite, setIgnite] = useState(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useMotionValueEvent(scrollYProgress, "change", (latestScrollValue) => {
|
useMotionValueEvent(scrollYProgress, "change", (latestScrollValue) => {
|
||||||
if (latestScrollValue > 0.54) {
|
if (latestScrollValue > 0.54) {
|
||||||
setFlapOpen(false);
|
setFlapOpen(false);
|
||||||
} else {
|
} else {
|
||||||
setFlapOpen(true);
|
setFlapOpen(true);
|
||||||
}
|
}
|
||||||
if (latestScrollValue <= 0.6) {
|
if (latestScrollValue <= 0.6) {
|
||||||
setIsEnvelopeFlipped(true);
|
setIsEnvelopeFlipped(true);
|
||||||
} else {
|
} else {
|
||||||
setIsEnvelopeFlipped(false);
|
setIsEnvelopeFlipped(false);
|
||||||
}
|
}
|
||||||
if (latestScrollValue > 0.68) {
|
if (latestScrollValue > 0.68) {
|
||||||
setRecipient("future me");
|
setRecipient("future me");
|
||||||
} else {
|
} else {
|
||||||
setRecipient("someone dear");
|
setRecipient("someone dear");
|
||||||
}
|
}
|
||||||
if (latestScrollValue > 0.77) {
|
if (latestScrollValue > 0.77) {
|
||||||
setIgnite(true);
|
setIgnite(true);
|
||||||
} else {
|
} else {
|
||||||
setIgnite(false);
|
setIgnite(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactLenis root options={{ lerp: 0.1, duration: 1.5, smoothWheel: true }}>
|
<ReactLenis root options={{ lerp: 0.1, duration: 1.5, smoothWheel: true }}>
|
||||||
<section
|
<section
|
||||||
ref={sectionContainer1}
|
ref={sectionContainer1}
|
||||||
className="relative w-full h-[850vh] bg-base-100 font-serif"
|
className="relative w-full h-[850vh] bg-base-100 font-serif"
|
||||||
|
>
|
||||||
|
<div className="sticky top-0 h-screen w-full flex flex-col items-center justify-center overflow-hidden">
|
||||||
|
{/* Intro */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute flex flex-col items-center justify-center pointer-events-none"
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(scrollYProgress, [0, 0.12, 1], [1, 0, 0]),
|
||||||
|
scale: useTransform(scrollYProgress, [0, 0.12], [1, 10]),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h1 className="text-neutral text-4xl md:text-6xl text-center px-6">
|
||||||
|
You've been carrying something
|
||||||
|
</h1>
|
||||||
|
<motion.h2 className="text-primary text-5xl md:text-7xl mt-4 italic font-display font-light">
|
||||||
|
unsaid
|
||||||
|
</motion.h2>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="absolute text-center"
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(scrollYProgress, [0, 0.15, 0.2], [0, 1, 0]),
|
||||||
|
y: useTransform(scrollYProgress, [0, 0.15, 0.2], [40, 0, -40]),
|
||||||
|
scale: useTransform(scrollYProgress, [0, 0.15, 0.2], [0.8, 1, 3]),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="mt-6 text-4xl md:text-6xl text-base-content/60 italic">
|
||||||
|
and that's okay...
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
{/* pi. ku. */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute text-center px-6"
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.18, 0.25, 0.3],
|
||||||
|
[0, 1, 0],
|
||||||
|
),
|
||||||
|
y: useTransform(scrollYProgress, [0.18, 0.25, 0.3], [20, 0, -20]),
|
||||||
|
}}
|
||||||
|
transition={{ delay: 4 }}
|
||||||
|
>
|
||||||
|
<Logo type="logo" scale={1.5} ul={true} />
|
||||||
|
<motion.div
|
||||||
|
className="font-serif font-extralight mt-6 text-4xl md:text-6xl text-neutral "
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.22, 0.25, 0.35, 0.4],
|
||||||
|
[0, 1, 1, 0],
|
||||||
|
),
|
||||||
|
y: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.25, 0.3, 0.35, 0.4],
|
||||||
|
[20, 0, 0, -20],
|
||||||
|
),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="sticky top-0 h-screen w-full flex flex-col items-center justify-center overflow-hidden">
|
is a{" "}
|
||||||
{/* Intro */}
|
<span className="font-display text-primary font-extralight">
|
||||||
<motion.div
|
safe space
|
||||||
className="absolute flex flex-col items-center justify-center pointer-events-none"
|
</span>
|
||||||
style={{
|
,<br />
|
||||||
opacity: useTransform(scrollYProgress, [0, 0.12, 1], [1, 0, 0]),
|
<motion.span
|
||||||
scale: useTransform(scrollYProgress, [0, 0.12], [1, 10]),
|
className="opacity-0 text-2xl md:text-4xl font-hand tracking-[0.4em] text-neutral"
|
||||||
}}
|
transition={{ delay: 5 }}
|
||||||
>
|
whileInView={{ opacity: 1 }}
|
||||||
<h1 className="text-neutral text-4xl md:text-6xl text-center px-6">
|
viewport={{ once: false, amount: 0.3 }}
|
||||||
You've been carrying something
|
>
|
||||||
</h1>
|
where you can
|
||||||
<motion.h2 className="text-primary text-5xl md:text-7xl mt-4 italic font-display font-light">
|
</motion.span>
|
||||||
unsaid
|
</motion.div>
|
||||||
</motion.h2>
|
</motion.div>
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
<div className="relative w-full max-w-5xl h-1/2 flex items-center justify-center mt-20">
|
||||||
className="absolute text-center"
|
<motion.h2
|
||||||
style={{
|
style={{
|
||||||
opacity: useTransform(scrollYProgress, [0, 0.15, 0.2], [0, 1, 0]),
|
opacity: useTransform(
|
||||||
y: useTransform(scrollYProgress, [0, 0.15, 0.2], [40, 0, -40]),
|
scrollYProgress,
|
||||||
scale: useTransform(scrollYProgress, [0, 0.15, 0.2], [0.8, 1, 3]),
|
[0.3, 0.35, 0.4, 0.45],
|
||||||
}}
|
[0, 1, 1, 0],
|
||||||
>
|
),
|
||||||
<div className="mt-6 text-4xl md:text-6xl text-base-content/60 italic">
|
y: useTransform(
|
||||||
and that's okay...
|
scrollYProgress,
|
||||||
</div>
|
[0.3, 0.35, 0.4, 0.45],
|
||||||
</motion.div>
|
[40, 0, 0, -40],
|
||||||
{/* pi. ku. */}
|
),
|
||||||
<motion.div
|
}}
|
||||||
className="absolute text-center px-6"
|
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
||||||
style={{
|
>
|
||||||
opacity: useTransform(
|
pen down your unsaid words into{" "}
|
||||||
scrollYProgress,
|
<span className="font-display text-primary font-extralight">
|
||||||
[0.18, 0.25, 0.3],
|
letters
|
||||||
[0, 1, 0],
|
</span>
|
||||||
),
|
.
|
||||||
y: useTransform(scrollYProgress, [0.18, 0.25, 0.3], [20, 0, -20]),
|
</motion.h2>
|
||||||
}}
|
{/* Seal */}
|
||||||
transition={{ delay: 4 }}
|
<motion.h2
|
||||||
>
|
style={{
|
||||||
<Logo type="logo" scale={1.5} ul={true} />
|
opacity: useTransform(
|
||||||
<motion.div
|
scrollYProgress,
|
||||||
className="font-serif font-extralight mt-6 text-4xl md:text-6xl text-neutral "
|
[0.45, 0.5, 0.55, 0.6],
|
||||||
style={{
|
[0, 1, 1, 0],
|
||||||
opacity: useTransform(
|
),
|
||||||
scrollYProgress,
|
y: useTransform(
|
||||||
[0.22, 0.25, 0.35, 0.4],
|
scrollYProgress,
|
||||||
[0, 1, 1, 0],
|
[0.45, 0.5, 0.55, 0.6],
|
||||||
),
|
[40, 0, 0, -40],
|
||||||
y: useTransform(
|
),
|
||||||
scrollYProgress,
|
}}
|
||||||
[0.25, 0.3, 0.35, 0.4],
|
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
||||||
[20, 0, 0, -20],
|
>
|
||||||
),
|
seal it{" "}
|
||||||
}}
|
<span className="text-success font-mono tracking-tighter font-extrabold">
|
||||||
>
|
secure
|
||||||
is a{" "}
|
</span>{" "}
|
||||||
<span className="font-display text-primary font-extralight">
|
and{" "}
|
||||||
safe space
|
<span className="text-info font-mono tracking-tighter">
|
||||||
</span>
|
private
|
||||||
,<br />
|
</span>
|
||||||
<motion.span
|
.
|
||||||
className="opacity-0 text-2xl md:text-4xl font-hand tracking-[0.4em] text-neutral"
|
</motion.h2>
|
||||||
transition={{ delay: 5 }}
|
{/* Send / vault */}
|
||||||
whileInView={{ opacity: 1 }}
|
<motion.h2
|
||||||
viewport={{ once: false, amount: 0.3 }}
|
style={{
|
||||||
>
|
opacity: useTransform(
|
||||||
where you can
|
scrollYProgress,
|
||||||
</motion.span>
|
[0.6, 0.63, 0.72, 0.75],
|
||||||
</motion.div>
|
[0, 1, 1, 0],
|
||||||
</motion.div>
|
),
|
||||||
|
y: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.6, 0.63, 0.72, 0.75],
|
||||||
|
[40, 0, 0, -40],
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
||||||
|
>
|
||||||
|
send it to{" "}
|
||||||
|
<motion.span
|
||||||
|
className="font-display text-accent"
|
||||||
|
style={{
|
||||||
|
color: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.67, 1],
|
||||||
|
["var(--color-accent)", "var(--color-neutral)"],
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
someone dear
|
||||||
|
</motion.span>
|
||||||
|
<motion.span
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(scrollYProgress, [0.66, 0.7], [0, 1]),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.span
|
||||||
|
className="font-display text-accent"
|
||||||
|
style={{
|
||||||
|
color: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.67, 1],
|
||||||
|
["var(--color-accent)", "var(--color-neutral)"],
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
or{" "}
|
||||||
|
</motion.span>
|
||||||
|
<span className="font-display text-success">
|
||||||
|
yourself in the future
|
||||||
|
</span>
|
||||||
|
.
|
||||||
|
</motion.span>
|
||||||
|
</motion.h2>
|
||||||
|
{/* Burn */}
|
||||||
|
<motion.h2
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.75, 0.8, 0.85, 0.9],
|
||||||
|
[0, 1, 1, 0],
|
||||||
|
),
|
||||||
|
y: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.75, 0.8, 0.85, 0.9],
|
||||||
|
[40, 0, 0, -40],
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
||||||
|
>
|
||||||
|
and even <span className="font-display text-error">burn it</span>{" "}
|
||||||
|
to release the burden.
|
||||||
|
</motion.h2>
|
||||||
|
{/* Outro */}
|
||||||
|
<motion.h2
|
||||||
|
className={
|
||||||
|
"italic absolute text-4xl md:text-6xl text-center px-10 leading-tight text-neutral-content/50"
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(scrollYProgress, [0.9, 1], [0, 1]),
|
||||||
|
y: useTransform(scrollYProgress, [0.9, 1], [80, 0]),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
You've been carrying it long enough.
|
||||||
|
</motion.h2>
|
||||||
|
{/* CTA */}
|
||||||
|
<motion.div
|
||||||
|
className={
|
||||||
|
"z-100 absolute -bottom-12 md:bottom-0 font-hand flex flex-wrap md:flex-nowrap gap-4 md:gap-12 justify-center"
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(scrollYProgress, [0.98, 1], [0, 1]),
|
||||||
|
y: useTransform(scrollYProgress, [0.98, 1], [80, 0]),
|
||||||
|
display: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.96, 1],
|
||||||
|
["none", "flex"],
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
"md:opacity-50 hover:opacity-100 btn btn-ghost btn-wide md:btn-xl rounded-full font-extralight md:grayscale hover:grayscale-0 hover:-translate-y-1 transition-all duration-1000"
|
||||||
|
}
|
||||||
|
type={"button"}
|
||||||
|
onClick={() => navigate(ROUTES.ABOUT, { replace: true })}
|
||||||
|
>
|
||||||
|
<InfoIcon className={"text-primary"} />
|
||||||
|
Tell me More
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
"md:opacity-50 hover:opacity-100 btn rounded-full btn-primary btn-wide md:btn-xl md:grayscale-50 hover:grayscale-0 focus:grayscale-0 active:grayscale-0 hover:-translate-y-1 transition-all duration-1000"
|
||||||
|
}
|
||||||
|
type={"button"}
|
||||||
|
onClick={() => navigate(ROUTES.ONBOARD, { replace: true })}
|
||||||
|
>
|
||||||
|
I'm ready
|
||||||
|
</button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="relative w-full max-w-5xl h-1/2 flex items-center justify-center mt-20">
|
<div className="relative h-1/4 w-full flex flex-col items-center justify-center pointer-events-none">
|
||||||
<motion.h2
|
<motion.div
|
||||||
style={{
|
className={"z-21 absolute"}
|
||||||
opacity: useTransform(
|
style={{
|
||||||
scrollYProgress,
|
opacity: useTransform(
|
||||||
[0.3, 0.35, 0.4, 0.45],
|
scrollYProgress,
|
||||||
[0, 1, 1, 0],
|
[0.3, 0.4, 0.5, 0.52],
|
||||||
),
|
[0, 1, 0.1, 0],
|
||||||
y: useTransform(
|
),
|
||||||
scrollYProgress,
|
y: useTransform(
|
||||||
[0.3, 0.35, 0.4, 0.45],
|
scrollYProgress,
|
||||||
[40, 0, 0, -40],
|
[0.3, 0.45, 0.5],
|
||||||
),
|
[300, 0, 200],
|
||||||
}}
|
),
|
||||||
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
scale: useTransform(
|
||||||
>
|
scrollYProgress,
|
||||||
pen down your unsaid words into{" "}
|
[0.3, 0.4, 0.5],
|
||||||
<span className="font-display text-primary font-extralight">
|
[1, 1, 0.6],
|
||||||
letters
|
),
|
||||||
</span>
|
}}
|
||||||
.
|
>
|
||||||
</motion.h2>
|
<div className="mockup-phone w-[75vw] border-primary">
|
||||||
{/* Seal */}
|
<div className="mockup-phone-camera"></div>
|
||||||
<motion.h2
|
<div className="mockup-phone-display">
|
||||||
style={{
|
<img alt="letter" src="/screenshots/letter.webp" />
|
||||||
opacity: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.45, 0.5, 0.55, 0.6],
|
|
||||||
[0, 1, 1, 0],
|
|
||||||
),
|
|
||||||
y: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.45, 0.5, 0.55, 0.6],
|
|
||||||
[40, 0, 0, -40],
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
|
||||||
>
|
|
||||||
seal it{" "}
|
|
||||||
<span className="text-success font-mono tracking-tighter font-extrabold">
|
|
||||||
secure
|
|
||||||
</span>{" "}
|
|
||||||
and{" "}
|
|
||||||
<span className="text-info font-mono tracking-tighter">
|
|
||||||
private
|
|
||||||
</span>
|
|
||||||
.
|
|
||||||
</motion.h2>
|
|
||||||
{/* Send / vault */}
|
|
||||||
<motion.h2
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.6, 0.63, 0.72, 0.75],
|
|
||||||
[0, 1, 1, 0],
|
|
||||||
),
|
|
||||||
y: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.6, 0.63, 0.72, 0.75],
|
|
||||||
[40, 0, 0, -40],
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
|
||||||
>
|
|
||||||
send it to{" "}
|
|
||||||
<motion.span
|
|
||||||
className="font-display text-accent"
|
|
||||||
style={{
|
|
||||||
color: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.67, 1],
|
|
||||||
["var(--color-accent)", "var(--color-neutral)"],
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
someone dear
|
|
||||||
</motion.span>
|
|
||||||
<motion.span
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(scrollYProgress, [0.66, 0.7], [0, 1]),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<motion.span
|
|
||||||
className="font-display text-accent"
|
|
||||||
style={{
|
|
||||||
color: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.67, 1],
|
|
||||||
["var(--color-accent)", "var(--color-neutral)"],
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
or{" "}
|
|
||||||
</motion.span>
|
|
||||||
<span className="font-display text-success">
|
|
||||||
yourself in the future
|
|
||||||
</span>
|
|
||||||
.
|
|
||||||
</motion.span>
|
|
||||||
</motion.h2>
|
|
||||||
{/* Burn */}
|
|
||||||
<motion.h2
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.75, 0.8, 0.85, 0.9],
|
|
||||||
[0, 1, 1, 0],
|
|
||||||
),
|
|
||||||
y: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.75, 0.8, 0.85, 0.9],
|
|
||||||
[40, 0, 0, -40],
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
className="absolute text-4xl md:text-6xl text-center px-10 leading-tight"
|
|
||||||
>
|
|
||||||
and even <span className="font-display text-error">burn it</span>{" "}
|
|
||||||
to release the burden.
|
|
||||||
</motion.h2>
|
|
||||||
{/* Outro */}
|
|
||||||
<motion.h2
|
|
||||||
className={
|
|
||||||
"italic absolute text-4xl md:text-6xl text-center px-10 leading-tight text-neutral-content/50"
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(scrollYProgress, [0.9, 1], [0, 1]),
|
|
||||||
y: useTransform(scrollYProgress, [0.9, 1], [80, 0]),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
You've been carrying it long enough.
|
|
||||||
</motion.h2>
|
|
||||||
{/* CTA */}
|
|
||||||
<motion.div
|
|
||||||
className={
|
|
||||||
"z-100 absolute -bottom-12 md:bottom-0 font-hand flex flex-wrap md:flex-nowrap gap-4 md:gap-12 justify-center"
|
|
||||||
}
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(scrollYProgress, [0.98, 1], [0, 1]),
|
|
||||||
y: useTransform(scrollYProgress, [0.98, 1], [80, 0]),
|
|
||||||
display: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.96, 1],
|
|
||||||
["none", "flex"],
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
"md:opacity-50 hover:opacity-100 btn btn-ghost btn-wide md:btn-xl rounded-full font-extralight md:grayscale hover:grayscale-0 hover:-translate-y-1 transition-all duration-1000"
|
|
||||||
}
|
|
||||||
type={"button"}
|
|
||||||
onClick={() => navigate(ROUTES.ABOUT, { replace: true })}
|
|
||||||
>
|
|
||||||
<InfoIcon className={"text-primary"} />
|
|
||||||
Tell me More
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
"md:opacity-50 hover:opacity-100 btn rounded-full btn-primary btn-wide md:btn-xl md:grayscale-50 hover:grayscale-0 focus:grayscale-0 active:grayscale-0 hover:-translate-y-1 transition-all duration-1000"
|
|
||||||
}
|
|
||||||
type={"button"}
|
|
||||||
onClick={() => navigate(ROUTES.ONBOARD, { replace: true })}
|
|
||||||
>
|
|
||||||
I'm ready
|
|
||||||
</button>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative h-1/4 w-full flex flex-col items-center justify-center pointer-events-none">
|
|
||||||
<motion.div
|
|
||||||
className={"z-21 absolute"}
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.3, 0.4, 0.5, 0.52],
|
|
||||||
[0, 1, 0.1, 0],
|
|
||||||
),
|
|
||||||
y: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.3, 0.45, 0.5],
|
|
||||||
[300, 0, 200],
|
|
||||||
),
|
|
||||||
scale: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.3, 0.4, 0.5],
|
|
||||||
[1, 1, 0.6],
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="mockup-phone w-[75vw] border-primary">
|
|
||||||
<div className="mockup-phone-camera"></div>
|
|
||||||
<div className="mockup-phone-display">
|
|
||||||
<img alt="letter" src="/screenshots/letter.webp" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
|
||||||
{/* Envelope */}
|
|
||||||
<motion.div
|
|
||||||
className="absolute scale-50 md:scale-80 z-10"
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.4, 0.45, 0.5, 0.7, 0.9, 1],
|
|
||||||
[0, 0.6, 1, 1, 0.3, 0],
|
|
||||||
),
|
|
||||||
y: useTransform(scrollYProgress, [0.45, 0.5, 1], [600, 200, 0]),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EnvelopeReveal
|
|
||||||
isInteractive={false}
|
|
||||||
ignite={ignite}
|
|
||||||
recipient={recipient}
|
|
||||||
date={formatDate(new Date().toISOString())}
|
|
||||||
onRevealComplete={() => { }}
|
|
||||||
isFlip={isEnvelopeFlipped}
|
|
||||||
openFlap={flapOpen}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
{/* Saajan */}
|
|
||||||
<motion.div
|
|
||||||
className="fixed bottom-0 z-10 font-sans -mb-6 scale-85 md:scale-100 md:mb-0"
|
|
||||||
style={{
|
|
||||||
opacity: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.98, 0.995, 1],
|
|
||||||
[0, 0.5, 1],
|
|
||||||
),
|
|
||||||
y: useTransform(scrollYProgress, [0.98, 1], [50, -10]),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Saajan
|
|
||||||
message={
|
|
||||||
"I think we forget things\nif there is nobody to tell them."
|
|
||||||
}
|
|
||||||
position={"top"}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
{/* Orb */}
|
|
||||||
<motion.div
|
|
||||||
className="w-48 z-100 h-48 rounded-full blur-3xl opacity-20"
|
|
||||||
transition={{
|
|
||||||
backgroundColor: { ease: "easeIn", duration: 2 },
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
backgroundColor: useTransform(
|
|
||||||
scrollYProgress,
|
|
||||||
[0.45, 0.5, 0.7, 0.75, 1],
|
|
||||||
[
|
|
||||||
"var(--color-primary)",
|
|
||||||
"var(--color-secondary)",
|
|
||||||
"var(--color-accent)",
|
|
||||||
"var(--color-success)",
|
|
||||||
"var(--color-error)",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
scale: useTransform(scrollYProgress, [0, 1], [0.6, 2.5]),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="absolute border border-primary/5 w-64 h-64 rounded-full backdrop-blur-[1px]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</div>
|
||||||
</ReactLenis>
|
</motion.div>
|
||||||
);
|
{/* Envelope */}
|
||||||
|
<motion.div
|
||||||
|
className="absolute scale-50 md:scale-80 z-10"
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.4, 0.45, 0.5, 0.7, 0.9, 1],
|
||||||
|
[0, 0.6, 1, 1, 0.3, 0],
|
||||||
|
),
|
||||||
|
y: useTransform(scrollYProgress, [0.45, 0.5, 1], [600, 200, 0]),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EnvelopeReveal
|
||||||
|
isInteractive={false}
|
||||||
|
ignite={ignite}
|
||||||
|
recipient={recipient}
|
||||||
|
date={formatDate(new Date().toISOString())}
|
||||||
|
onRevealComplete={() => {}}
|
||||||
|
isFlip={isEnvelopeFlipped}
|
||||||
|
openFlap={flapOpen}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
{/* Saajan */}
|
||||||
|
<motion.div
|
||||||
|
className="fixed bottom-0 z-10 font-sans -mb-6 scale-85 md:scale-100 md:mb-0"
|
||||||
|
style={{
|
||||||
|
opacity: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.98, 0.995, 1],
|
||||||
|
[0, 0.5, 1],
|
||||||
|
),
|
||||||
|
y: useTransform(scrollYProgress, [0.98, 1], [50, -10]),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Saajan
|
||||||
|
message={
|
||||||
|
"I think we forget things\nif there is nobody to tell them."
|
||||||
|
}
|
||||||
|
position={"top"}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
{/* Orb */}
|
||||||
|
<motion.div
|
||||||
|
className="w-48 z-100 h-48 rounded-full blur-3xl opacity-20"
|
||||||
|
transition={{
|
||||||
|
backgroundColor: { ease: "easeIn", duration: 2 },
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
backgroundColor: useTransform(
|
||||||
|
scrollYProgress,
|
||||||
|
[0.45, 0.5, 0.7, 0.75, 1],
|
||||||
|
[
|
||||||
|
"var(--color-primary)",
|
||||||
|
"var(--color-secondary)",
|
||||||
|
"var(--color-accent)",
|
||||||
|
"var(--color-success)",
|
||||||
|
"var(--color-error)",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
scale: useTransform(scrollYProgress, [0, 1], [0.6, 2.5]),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="absolute border border-primary/5 w-64 h-64 rounded-full backdrop-blur-[1px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</ReactLenis>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user