diff --git a/package-lock.json b/package-lock.json
index ea63335..924fe43 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,8 @@
"dependencies": {
"canvas-confetti": "^1.9.4",
"react": "^19.2.0",
- "react-dom": "^19.2.0"
+ "react-dom": "^19.2.0",
+ "react-router-dom": "^7.13.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
@@ -1971,6 +1972,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2869,6 +2883,44 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz",
+ "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz",
+ "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.13.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -2940,6 +2992,12 @@
"semver": "bin/semver.js"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
diff --git a/package.json b/package.json
index d900bd4..5800c32 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,8 @@
"dependencies": {
"canvas-confetti": "^1.9.4",
"react": "^19.2.0",
- "react-dom": "^19.2.0"
+ "react-dom": "^19.2.0",
+ "react-router-dom": "^7.13.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
diff --git a/src/App.tsx b/src/App.tsx
index 9e20659..32806d2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,187 +1,12 @@
-import { useState } from "react";
-import { useRef } from "react";
-import confetti from "canvas-confetti";
-import "./App.css";
+import { Routes, Route } from "react-router-dom";
+import HomePage from "./HomePage";
+import YesPage from "./yes";
export default function App() {
- const blockbusterRef = useRef(null);
-
- const moveButton = () => {
- const button = blockbusterRef.current;
- const dialog = button.closest("dialog");
-
- const dialogRect = dialog.getBoundingClientRect();
- const buttonRect = button.getBoundingClientRect();
-
- const padding = 20;
-
- const maxX = dialogRect.width - buttonRect.width - padding;
- const maxY = dialogRect.height - buttonRect.height - padding;
-
- const x = Math.random() * maxX;
- const y = Math.random() * maxY;
-
- button.style.left = `${x}px`;
- button.style.top = `${y}px`;
-};
-
- const dialogRef = useRef(null);
-
- const openPopup = () => {
- dialogRef.current.showModal();
- };
-
- const closePopup = () => {
- dialogRef.current.close();
- };
- const [showConfirm, setShowConfirm] = useState(false);
- const [showConfirm2, setShowConfirm2] = useState(false);
- const [showConfirm3, setShowConfirm3] = useState(false);
- const [showConfirm4, setShowConfirm4] = useState(false);
- const [showDeclineQ, setShowDeclineQ] = useState(false);
- const [showDeclineOffer, setShowDeclineOffer] = useState(false);
-
- const handleYes = () => {
- const bursts = 9; // how many times it fires
-
- for (let i = 0; i < bursts; i++) {
- setTimeout(() => {
- confetti({
- particleCount: 80,
- spread: 70,
- origin: {
- x: Math.random(), // 0 → 1 (left to right)
- y: 0.9
-// y: Math.random() * 0.6 // top half of screen
- },
- });
- }, i * 200); // delay between bursts (ms)
- }
- };
-
- const handleNo = () => {
- setShowConfirm(true);
- };
-
- const handleYes2 = () => {
- setShowConfirm2(true);
- };
-
- const handleYes3 = () => {
- setShowConfirm3(true);
- };
-
- const handleYes4 = () => {
- setShowConfirm4(true);
- };
-
- const handleNo5 = () => {
- setShowDeclineQ(true);
- };
-
return (
-
-
Will you be my Valentine? 💖
-
-
-
Photo 1
-
Photo 2
-
Photo 3
-
-
-
-
-
-
-
- {showConfirm && (
-
-
-
Are you sure? 😢
-
-
-
-
- )}
- {showConfirm2 && (
-
-
-
Are you sure you're sure? 😭
-
-
-
-
- )}
- {showConfirm3 && (
-
-
-
Are you sure that you're sure of being sure? 💔😭
-
-
-
-
- )}
- {showConfirm4 && (
-
-
-
Have you tried being unsure? 🤞🙏
-
-
-
-
- )}
- {showDeclineQ && (
-
-
-
Would you like to decline to be my valentine? 💔😭😢
-
-
-
-
- )}
-
-
-
+
+ } />
+ } />
+
);
}
diff --git a/src/HomePage.tsx b/src/HomePage.tsx
new file mode 100644
index 0000000..5c9fbee
--- /dev/null
+++ b/src/HomePage.tsx
@@ -0,0 +1,236 @@
+import { useRef, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import confetti from "canvas-confetti";
+import hozier from "./assets/hozier.jpeg"
+import newberg_tree from "./assets/newberg_tree.jpeg"
+import newberg_tractor from "./assets/newberg_tractor.jpeg"
+import panic from "./assets/panic.jpeg"
+import corvallis from "./assets/corvallis.jpeg"
+import seal_rock from "./assets/seal_rock.jpeg"
+import yaquina_lighthouse from "./assets/yaquina_lighthouse.jpeg"
+import "./App.css";
+
+export default function Homepage() {
+ const navigate = useNavigate();
+
+ const dialogRef = useRef(null);
+ const blockbusterButtonRef = useRef(null);
+
+ const isTouchDevice = () =>
+ "ontouchstart" in window || navigator.maxTouchPoints > 0;
+
+ const [showConfirm, setShowConfirm] = useState(false);
+ const [showConfirm2, setShowConfirm2] = useState(false);
+ const [showConfirm3, setShowConfirm3] = useState(false);
+ const [showConfirm4, setShowConfirm4] = useState(false);
+ const [showDeclineQ, setShowDeclineQ] = useState(false);
+
+ const openPopup = () => {
+ dialogRef.current.showModal();
+
+ // Wait for layout to settle
+ requestAnimationFrame(() => {
+ const button = blockbusterButtonRef.current;
+ const rect = button.getBoundingClientRect();
+
+ button.style.left = `${rect.left}px`;
+ button.style.top = `${rect.top}px`;
+ });
+ };
+
+ /* ------------------------------------------------------------------
+ * Runaway Blockbuster button logic
+ * ------------------------------------------------------------------ */
+ const moveButton = () => {
+ const button = blockbusterButtonRef.current;
+
+ const buttonRect = button.getBoundingClientRect();
+
+ const padding = 16;
+
+ const maxX = window.innerWidth - buttonRect.width - padding;
+ const maxY = window.innerHeight - buttonRect.height - padding;
+
+ const x = Math.random() * maxX;
+ const y = Math.random() * maxY;
+
+ button.style.left = `${Math.max(padding, x)}px`;
+ button.style.top = `${Math.max(padding, y)}px`;
+ };
+
+
+ const handleRunawayPress = (event) => {
+ if (isTouchDevice()) {
+ event.preventDefault();
+ event.stopPropagation();
+ moveButton();
+ }
+ };
+
+ /* ------------------------------------------------------------------
+ * Button handlers
+ * ------------------------------------------------------------------ */
+ const handleAccept = () => {
+ const bursts = 9;
+
+ for (let i = 0; i < bursts; i++) {
+ setTimeout(() => {
+ confetti({
+ particleCount: 80,
+ spread: 70,
+ origin: {
+ x: Math.random(),
+ y: 0.9,
+ // y: Math.random() * 0.6
+ },
+ });
+ }, i * 200);
+ }
+ };
+
+ const handleNo = () => {
+ setShowConfirm(true);
+ };
+
+ const handleYes2 = () => {
+ setShowConfirm2(true);
+ };
+
+ const handleYes3 = () => {
+ setShowConfirm3(true);
+ };
+
+ const handleYes4 = () => {
+ setShowConfirm4(true);
+ };
+
+ const handleNo5 = () => {
+ setShowDeclineQ(true);
+ };
+
+ /* ------------------------------------------------------------------
+ * Render
+ * ------------------------------------------------------------------ */
+ return (
+
+
Will you be my Valentine? 💖
+
+
+
+
+
+
+
+
+ {showConfirm && (
+
+
+
Are you sure? 😢
+
+
+
+
+ )}
+
+ {showConfirm2 && (
+
+
+
Are you sure you're sure? 😭
+
+
+
+
+ )}
+
+ {showConfirm3 && (
+
+
+
Are you sure that you're sure of being sure? 💔😭
+
+
+
+
+ )}
+
+ {showConfirm4 && (
+
+
+
Have you tried being unsure? 🤞🙏
+
+
+
+
+ )}
+
+ {showDeclineQ && (
+
+
+
Would you like to decline to be my valentine? 💔😭😢
+
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/src/assets/corvallis.jpeg b/src/assets/corvallis.jpeg
new file mode 100644
index 0000000..7374abb
Binary files /dev/null and b/src/assets/corvallis.jpeg differ
diff --git a/src/assets/hozier.jpeg b/src/assets/hozier.jpeg
new file mode 100644
index 0000000..5558e7e
Binary files /dev/null and b/src/assets/hozier.jpeg differ
diff --git a/src/assets/newberg_tractor.jpeg b/src/assets/newberg_tractor.jpeg
new file mode 100644
index 0000000..f7f6312
Binary files /dev/null and b/src/assets/newberg_tractor.jpeg differ
diff --git a/src/assets/newberg_tree.jpeg b/src/assets/newberg_tree.jpeg
new file mode 100644
index 0000000..9ef535c
Binary files /dev/null and b/src/assets/newberg_tree.jpeg differ
diff --git a/src/assets/panic.jpeg b/src/assets/panic.jpeg
new file mode 100644
index 0000000..7251792
Binary files /dev/null and b/src/assets/panic.jpeg differ
diff --git a/src/assets/seal_rock.jpeg b/src/assets/seal_rock.jpeg
new file mode 100644
index 0000000..1f79fa5
Binary files /dev/null and b/src/assets/seal_rock.jpeg differ
diff --git a/src/assets/yaquina_lighthouse.jpeg b/src/assets/yaquina_lighthouse.jpeg
new file mode 100644
index 0000000..b028c69
Binary files /dev/null and b/src/assets/yaquina_lighthouse.jpeg differ
diff --git a/src/index.css b/src/index.css
index 1b95ec4..6070478 100644
--- a/src/index.css
+++ b/src/index.css
@@ -42,7 +42,7 @@ button {
font-size: 1em;
font-weight: 500;
font-family: inherit;
- background-color: #1a1a1a;
+ background-color: #531300;
cursor: pointer;
transition: border-color 0.25s;
}
@@ -81,9 +81,33 @@ dialog::backdrop {
.modal {
position: relative;
+ overflow: hidden; /* prevent scrollbars */
}
.runaway {
- position: absolute;
- transition: left 0.15s ease, top 0.15s ease;
+ position: fixed; /* escape the dialog */
+ z-index: 9999; /* above everything */
+ transition: left 0.12s ease, top 0.12s ease;
+}
+
+.photos {
+ display: flex;
+ flex-wrap: wrap; /* allow photos to wrap to next line */
+ gap: 12px; /* space between photos */
+ justify-content: center; /* center on screen */
+ margin-bottom: 24px;
+}
+
+
+.photo img {
+ width: 150px;
+ height: 150px;
+ object-fit: cover;
+ border-radius: 12px;
+ box-shadow: 0 4px 10px rgba(0,0,0,0.15);
+ transition: transform 0.2s;
+}
+
+.photo img:hover {
+ transform: scale(1.05);
}
diff --git a/src/main.tsx b/src/main.tsx
index bef5202..d81c2dc 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,13 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.tsx'
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { BrowserRouter } from "react-router-dom";
+import App from "./App";
+import "./index.css"; // your global styles
-createRoot(document.getElementById('root')!).render(
-
-
- ,
-)
+ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
+
+
+
+
+
+);
diff --git a/src/yes.tsx b/src/yes.tsx
new file mode 100644
index 0000000..858d001
--- /dev/null
+++ b/src/yes.tsx
@@ -0,0 +1,9 @@
+export default function YesPage() {
+ return (
+
+
Congratulations, you're now my valentine!!! 💖🥳
+ Great job using the freewill the website gave you to make the right choice!!!
+ No takebacks allowed, you're stuck with me 🎉
+
+ );
+}
diff --git a/vite.config.ts b/vite.config.ts
index 8b0f57b..259be20 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -4,4 +4,7 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
+ server: {
+ host: true,
+ },
})