This commit is contained in:
h202-wq
2026-08-18 18:20:27 -04:00
commit 9796ddeac3
15 changed files with 1695 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules
dist
.git
.gitignore
Dockerfile
.dockerignore
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+11
View File
@@ -0,0 +1,11 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM nginx:1.27-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+21
View File
@@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="One verse of scripture is appointed for each day. Seek it." />
<meta name="theme-color" content="#0b0806" />
<title>Walk By Faith</title>
<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=Cinzel:wght@400..700&family=EB+Garamond:ital,wght@0,400;0,500;1,400&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+882
View File
@@ -0,0 +1,882 @@
{
"name": "walkbyfaith",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "walkbyfaith",
"version": "0.0.0",
"dependencies": {
"react": "^19.2.8",
"react-dom": "^19.2.8"
},
"devDependencies": {
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.0.5",
"typescript": "~6.0.2",
"vite": "^8.2.0"
}
},
"node_modules/@oxc-project/types": {
"version": "0.144.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz",
"integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",
"integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz",
"integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz",
"integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz",
"integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz",
"integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz",
"integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz",
"integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz",
"integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz",
"integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz",
"integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz",
"integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz",
"integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz",
"integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz",
"integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
"node_modules/@vitejs/plugin-react": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.5.tgz",
"integrity": "sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rolldown/pluginutils": "^1.0.1"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
"babel-plugin-react-compiler": "^1.0.0",
"vite": "^8.0.0"
},
"peerDependenciesMeta": {
"@rolldown/plugin-babel": {
"optional": true
},
"babel-plugin-react-compiler": {
"optional": true
}
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.33.0",
"lightningcss-darwin-arm64": "1.33.0",
"lightningcss-darwin-x64": "1.33.0",
"lightningcss-freebsd-x64": "1.33.0",
"lightningcss-linux-arm-gnueabihf": "1.33.0",
"lightningcss-linux-arm64-gnu": "1.33.0",
"lightningcss-linux-arm64-musl": "1.33.0",
"lightningcss-linux-x64-gnu": "1.33.0",
"lightningcss-linux-x64-musl": "1.33.0",
"lightningcss-win32-arm64-msvc": "1.33.0",
"lightningcss-win32-x64-msvc": "1.33.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
"integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
"integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
"integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
"integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
"integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
"integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
"integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
"integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
"integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
"integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.26",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
"integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.17",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/react": {
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.8"
}
},
"node_modules/rolldown": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz",
"integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.144.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.2.4",
"@rolldown/binding-darwin-arm64": "1.2.4",
"@rolldown/binding-darwin-x64": "1.2.4",
"@rolldown/binding-freebsd-x64": "1.2.4",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.4",
"@rolldown/binding-linux-arm64-gnu": "1.2.4",
"@rolldown/binding-linux-arm64-musl": "1.2.4",
"@rolldown/binding-linux-ppc64-gnu": "1.2.4",
"@rolldown/binding-linux-s390x-gnu": "1.2.4",
"@rolldown/binding-linux-x64-gnu": "1.2.4",
"@rolldown/binding-linux-x64-musl": "1.2.4",
"@rolldown/binding-openharmony-arm64": "1.2.4",
"@rolldown/binding-win32-arm64-msvc": "1.2.4",
"@rolldown/binding-win32-x64-msvc": "1.2.4"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/typescript": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/vite": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.2.1.tgz",
"integrity": "sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.5",
"postcss": "^8.5.25",
"rolldown": "~1.2.1",
"tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.4.0",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"@vitejs/devtools": {
"optional": true
},
"esbuild": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
{
"name": "walkbyfaith",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.0.5",
"typescript": "~6.0.2",
"vite": "^8.2.0"
},
"dependencies": {
"react": "^19.2.8",
"react-dom": "^19.2.8"
}
}
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="7" fill="#0b0806"/>
<path d="M16 3.5C16 3.5 7 12.5 7 19a9 9 0 0 0 18 0c0-6.5-9-15.5-9-15.5z" fill="#f0a94f"/>
<path d="M16 11c0 0-4.4 5.6-4.4 9.1a4.4 4.4 0 0 0 8.8 0c0-3.5-4.4-9.1-4.4-9.1z" fill="#ffd9a0"/>
<path d="M16 2.2 16.9 3.6 18.3 4.5 16.9 5.4 16 6.8 15.1 5.4 13.7 4.5 15.1 3.6z" fill="#ffd9a0"/>
</svg>

After

Width:  |  Height:  |  Size: 412 B

+1
View File
File diff suppressed because one or more lines are too long
+103
View File
@@ -0,0 +1,103 @@
import { useCallback, useEffect, useState } from 'react'
import CandleCanvas from './Candle'
import { daySeed, loadKJV, msUntilMidnightUtc, pickForDay, type Verse } from './verse'
type Phase = 'sealed' | 'revealing' | 'revealed'
function formatCountdown(ms: number): string {
const s = Math.max(0, Math.ceil(ms / 1000))
const h = Math.floor(s / 3600)
const m = Math.floor((s % 3600) / 60)
const sec = s % 60
return `${String(h).padStart(2, '0')}h ${String(m).padStart(2, '0')}m ${String(sec).padStart(2, '0')}s`
}
export default function App() {
const [phase, setPhase] = useState<Phase>('sealed')
const [verse, setVerse] = useState<Verse | null>(null)
const [countdown, setCountdown] = useState('')
const [error, setError] = useState(false)
useEffect(() => {
const update = () => setCountdown(formatCountdown(msUntilMidnightUtc()))
update()
const id = window.setInterval(update, 1000)
return () => window.clearInterval(id)
}, [])
const seek = useCallback(async () => {
if (phase !== 'sealed') return
setPhase('revealing')
try {
const verses = await loadKJV()
setVerse(pickForDay(verses, daySeed()))
setPhase('revealed')
} catch {
setError(true)
setPhase('sealed')
}
}, [phase])
const today = new Date().toLocaleDateString(undefined, {
year: 'numeric',
month: 'long',
day: 'numeric',
})
return (
<div className="app">
<CandleCanvas />
<div className="noise" aria-hidden="true" />
<div className="vignette" aria-hidden="true" />
<header className="app-header">
<h1 className="title">Walk By Faith</h1>
<p className="tagline">
For we walk by faith, not by sight. <span className="tagline-ref"> 2 Corinthians 5:7</span>
</p>
</header>
<main className="app-main">
{phase !== 'revealed' && (
<section className="sealed" aria-label="Today's verse is sealed">
<div className="seal">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path
d="M12 1.8C12 1.8 4.6 9.4 4.6 15a7.4 7.4 0 0 0 14.8 0c0-5.6-7.4-13.2-7.4-13.2z"
fill="none"
stroke="currentColor"
strokeWidth="1.1"
/>
<path
d="M12 8.4c0 0-3 4.2-3 7a3 3 0 0 0 6 0c0-2.8-3-7-3-7z"
fill="currentColor"
/>
</svg>
</div>
<p className="seal-caption">The verse for this day is sealed</p>
<button className="seek-btn" onClick={seek} disabled={phase === 'revealing'}>
{error ? 'Relight the lamp' : phase === 'revealing' ? 'Lighting the lamp…' : 'Seek the verse'}
</button>
</section>
)}
{phase === 'revealed' && verse && (
<section className="revealed" aria-live="polite">
<p className="reveal-label">The verse for {today}</p>
<div className="verse">
<p className="verse-text">{verse.text}</p>
<p className="verse-ref"> {verse.reference}</p>
</div>
</section>
)}
</main>
<footer className="app-footer">
<p className="countdown">
A new verse is appointed in <strong>{countdown}</strong>
</p>
<p className="foot-note">King James Version · Public Domain · 31,102 verses</p>
</footer>
</div>
)
}
+233
View File
@@ -0,0 +1,233 @@
import { useEffect, useRef } from 'react'
interface Ember {
x: number
y: number
vx: number
vy: number
r: number
life: number
maxLife: number
drift: number
}
export default function CandleCanvas() {
const canvasRef = useRef<HTMLCanvasElement | null>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
let w = 0
let h = 0
let dpr = 1
let raf = 0
const embers: Ember[] = []
const resize = () => {
dpr = Math.min(window.devicePixelRatio || 1, 2)
w = window.innerWidth
h = window.innerHeight
canvas.width = Math.round(w * dpr)
canvas.height = Math.round(h * dpr)
canvas.style.width = `${w}px`
canvas.style.height = `${h}px`
ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
}
resize()
window.addEventListener('resize', resize)
const rand = (a: number, b: number) => a + Math.random() * (b - a)
const cx = () => w / 2
const flameBaseY = () => h * 0.78
const flameH = () => 84 * Math.min(1, w / 760) + 26
const flameW = () => flameH() * 0.52
const flameTipY = () => flameBaseY() - flameH()
const spawnEmber = () => {
embers.push({
x: cx() + rand(-flameW() * 0.5, flameW() * 0.5),
y: flameTipY() + rand(0, 8),
vx: rand(-0.3, 0.3),
vy: rand(-0.75, -0.4),
r: rand(0.7, 1.9),
life: 0,
maxLife: rand(240, 520),
drift: rand(0, Math.PI * 2),
})
}
const drawGlow = (t: number) => {
const gx = cx()
const gy = flameBaseY() - flameH() * 0.4
const pulse = 0.5 + 0.5 * Math.sin(t * 0.0013)
const radius = Math.max(w, h) * 0.5
const glow = ctx.createRadialGradient(gx, gy, 10, gx, gy, radius)
glow.addColorStop(0, `rgba(255, 168, 82, ${0.15 + 0.05 * pulse})`)
glow.addColorStop(0.4, `rgba(200, 110, 45, ${0.055 + 0.02 * pulse})`)
glow.addColorStop(1, 'rgba(0, 0, 0, 0)')
ctx.fillStyle = glow
ctx.fillRect(0, 0, w, h)
}
const drawWax = () => {
const x = cx()
const top = flameBaseY() + 10
const bottom = h + 40
const half = 30 * Math.min(1, w / 760) + 12
const left = x - half
const right = x + half
const body = ctx.createLinearGradient(left, 0, right, 0)
body.addColorStop(0, '#d9c9a4')
body.addColorStop(0.5, '#f2e6c8')
body.addColorStop(1, '#cbbb94')
ctx.fillStyle = body
ctx.beginPath()
ctx.moveTo(left, top)
ctx.lineTo(right, top)
ctx.lineTo(right, bottom)
ctx.lineTo(left, bottom)
ctx.closePath()
ctx.fill()
const drop = ctx.createLinearGradient(left, 0, right, 0)
drop.addColorStop(0, '#d9c9a4')
drop.addColorStop(1, '#cbbb94')
ctx.fillStyle = drop
for (let i = 0; i < 3; i++) {
const dx = left - 5 + i * 5
const dy = top + 14 + i * 22
const len = 26 + i * 9
ctx.beginPath()
ctx.moveTo(dx, dy - 12)
ctx.quadraticCurveTo(dx + 4, dy, dx + 4, dy + len)
ctx.quadraticCurveTo(dx + 4, dy + len + 7, dx, dy + len + 7)
ctx.quadraticCurveTo(dx - 4, dy + len + 7, dx - 4, dy + len)
ctx.quadraticCurveTo(dx - 4, dy, dx, dy - 12)
ctx.closePath()
ctx.fill()
}
const topEdge = ctx.createLinearGradient(0, top, 0, top + 10)
topEdge.addColorStop(0, '#fdf3da')
topEdge.addColorStop(1, 'rgba(253, 243, 218, 0)')
ctx.fillStyle = topEdge
ctx.fillRect(left, top - 2, half * 2, 10)
ctx.strokeStyle = '#2b2017'
ctx.lineWidth = 2.5
ctx.lineCap = 'round'
ctx.beginPath()
ctx.moveTo(x, flameBaseY())
ctx.lineTo(x, top)
ctx.stroke()
}
const drawFlame = (t: number) => {
const fh = flameH() * (1 + 0.09 * Math.sin(t * 0.011) + 0.045 * Math.sin(t * 0.031 + 1.7))
const fw = flameW() * (1 + 0.08 * Math.sin(t * 0.017 + 0.6) + 0.04 * Math.sin(t * 0.043))
const tipX = cx()
const tipY = flameTipY() - (fh - flameH()) * 0.6
const baseY = tipY + fh
const outer = ctx.createRadialGradient(tipX, baseY - fh * 0.45, 2, tipX, baseY - fh * 0.5, fw * 2.2)
outer.addColorStop(0, 'rgba(255, 244, 214, 0.95)')
outer.addColorStop(0.35, 'rgba(255, 205, 120, 0.85)')
outer.addColorStop(0.7, 'rgba(240, 140, 60, 0.45)')
outer.addColorStop(1, 'rgba(240, 120, 40, 0)')
ctx.save()
ctx.translate(tipX, baseY)
ctx.scale(1, -1)
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.quadraticCurveTo(fw * 0.75, fh * 0.15, fw * 0.34, fh * 0.72)
ctx.quadraticCurveTo(fw * 0.12, fh * 0.97, 0, fh)
ctx.quadraticCurveTo(-fw * 0.12, fh * 0.97, -fw * 0.34, fh * 0.72)
ctx.quadraticCurveTo(-fw * 0.75, fh * 0.15, 0, 0)
ctx.closePath()
ctx.fillStyle = outer
ctx.fill()
const ih = fh * 0.42
const iw = fw * 0.4
ctx.beginPath()
ctx.moveTo(0, fh * 0.16)
ctx.quadraticCurveTo(iw * 0.7, fh * 0.16 + ih * 0.28, iw * 0.3, fh * 0.16 + ih * 0.82)
ctx.quadraticCurveTo(iw * 0.1, fh * 0.16 + ih * 1.05, 0, fh * 0.16 + ih)
ctx.quadraticCurveTo(-iw * 0.1, fh * 0.16 + ih * 1.05, -iw * 0.3, fh * 0.16 + ih * 0.82)
ctx.quadraticCurveTo(-iw * 0.7, fh * 0.16 + ih * 0.28, 0, fh * 0.16)
ctx.closePath()
const inner = ctx.createRadialGradient(0, fh * 0.55, 1, 0, fh * 0.58, iw * 1.4)
inner.addColorStop(0, 'rgba(255, 252, 240, 1)')
inner.addColorStop(0.6, 'rgba(255, 226, 160, 0.9)')
inner.addColorStop(1, 'rgba(255, 190, 90, 0)')
ctx.fillStyle = inner
ctx.fill()
ctx.restore()
}
const drawEmbers = () => {
ctx.globalCompositeOperation = 'lighter'
for (let i = embers.length - 1; i >= 0; i--) {
const e = embers[i]
e.life += 1
e.x += e.vx + Math.sin(e.life * 0.02 + e.drift) * 0.3
e.y += e.vy
if (e.life >= e.maxLife || e.y < -30) {
embers.splice(i, 1)
continue
}
const alpha = Math.max(0, 1 - e.life / e.maxLife)
const grad = ctx.createRadialGradient(e.x, e.y, 0, e.x, e.y, e.r * 3.2)
grad.addColorStop(0, `rgba(255, 214, 140, ${alpha})`)
grad.addColorStop(0.5, `rgba(240, 150, 70, ${alpha * 0.6})`)
grad.addColorStop(1, 'rgba(240, 120, 40, 0)')
ctx.fillStyle = grad
ctx.beginPath()
ctx.arc(e.x, e.y, e.r * 3.2, 0, Math.PI * 2)
ctx.fill()
}
ctx.globalCompositeOperation = 'source-over'
}
const draw = (t: number) => {
ctx.clearRect(0, 0, w, h)
drawGlow(t)
drawWax()
drawFlame(t)
if (!reduced) {
if (embers.length < 70 && Math.random() < 0.14) spawnEmber()
drawEmbers()
}
}
const loop = (t: number) => {
draw(t)
raf = requestAnimationFrame(loop)
}
if (reduced) {
draw(0)
} else {
raf = requestAnimationFrame(loop)
}
return () => {
cancelAnimationFrame(raf)
window.removeEventListener('resize', resize)
}
}, [])
return <canvas ref={canvasRef} className="candle-canvas" aria-hidden="true" />
}
+10
View File
@@ -0,0 +1,10 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './style.css'
import App from './App'
createRoot(document.getElementById('app')!).render(
<StrictMode>
<App />
</StrictMode>,
)
+275
View File
@@ -0,0 +1,275 @@
:root {
--bg: #0b0806;
--ink: #ecd9b4;
--ink-dim: #a08a68;
--ink-faint: rgba(160, 138, 104, 0.55);
--accent: #f0a94f;
--accent-soft: #ffd9a0;
--line: rgba(240, 169, 79, 0.28);
--serif: 'EB Garamond', Georgia, 'Times New Roman', serif;
--display: 'Cinzel', 'EB Garamond', Georgia, serif;
}
* {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--serif);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
.candle-canvas {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
}
.noise {
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
opacity: 0.05;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.7'/></svg>");
}
.vignette {
position: fixed;
inset: 0;
z-index: 1;
pointer-events: none;
background: radial-gradient(ellipse at 50% 40%, transparent 32%, rgba(0, 0, 0, 0.5) 72%, rgba(0, 0, 0, 0.86) 100%);
}
.app {
position: relative;
z-index: 2;
min-height: 100vh;
min-height: 100dvh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: clamp(1.5rem, 4vh, 3rem) clamp(1.25rem, 5vw, 3rem);
text-align: center;
}
.app-header {
width: 100%;
}
.title {
font-family: var(--display);
text-transform: uppercase;
letter-spacing: 0.42em;
text-indent: 0.42em;
font-weight: 600;
font-size: clamp(1.15rem, 3vw, 1.7rem);
color: var(--ink);
margin: 0;
}
.tagline {
margin: 1rem 0 0;
font-style: italic;
font-size: clamp(0.95rem, 2vw, 1.1rem);
color: var(--ink-dim);
}
.tagline-ref {
font-style: normal;
font-family: var(--display);
letter-spacing: 0.14em;
font-size: 0.78em;
color: var(--ink-faint);
}
.app-main {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
padding: 2.5rem 0;
}
.seal {
width: clamp(10rem, 26vw, 13rem);
aspect-ratio: 1;
border-radius: 50%;
border: 1px solid var(--line);
color: var(--accent);
box-shadow:
0 0 0 8px rgba(240, 169, 79, 0.05),
0 0 0 9px rgba(240, 169, 79, 0.16) inset,
0 0 60px rgba(240, 169, 79, 0.12);
display: grid;
place-items: center;
background: radial-gradient(circle at 50% 40%, rgba(240, 169, 79, 0.09), rgba(0, 0, 0, 0) 70%);
}
.seal svg {
width: 34%;
height: 34%;
opacity: 0.85;
}
.seal-caption {
margin: 1.6rem 0 0;
font-family: var(--display);
letter-spacing: 0.28em;
text-indent: 0.28em;
text-transform: uppercase;
font-size: 0.72rem;
color: var(--ink-dim);
}
.seek-btn {
margin-top: 2.2rem;
font-family: var(--display);
text-transform: uppercase;
letter-spacing: 0.3em;
text-indent: 0.3em;
font-size: 0.82rem;
color: var(--accent-soft);
background: transparent;
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.95rem 2.6rem;
cursor: pointer;
animation: seal-breathe 3.2s ease-in-out infinite;
transition:
box-shadow 0.5s,
background 0.5s,
border-color 0.5s,
color 0.5s;
}
.seek-btn:hover {
box-shadow:
0 0 24px rgba(240, 169, 79, 0.35),
inset 0 0 18px rgba(240, 169, 79, 0.12);
background: rgba(240, 169, 79, 0.06);
border-color: rgba(240, 169, 79, 0.55);
color: #fff0d4;
}
.seek-btn:focus-visible {
outline: 1px solid var(--accent);
outline-offset: 4px;
}
.seek-btn:disabled {
opacity: 0.65;
cursor: wait;
}
@keyframes seal-breathe {
0%,
100% {
box-shadow: 0 0 8px rgba(240, 169, 79, 0.12);
}
50% {
box-shadow: 0 0 26px rgba(240, 169, 79, 0.3);
}
}
.reveal-label {
font-family: var(--display);
letter-spacing: 0.3em;
text-indent: 0.3em;
text-transform: uppercase;
font-size: 0.68rem;
color: var(--ink-dim);
margin: 0 0 1.8rem;
}
.verse {
max-width: 46rem;
margin: 0 auto;
animation: rise 1.6s ease-out both;
}
.verse-text {
font-size: clamp(1.35rem, 4.2vw, 2.3rem);
line-height: 1.55;
color: var(--accent-soft);
font-style: italic;
margin: 0;
}
.verse-text::first-letter {
font-size: 2.6em;
float: left;
line-height: 0.9;
padding-right: 0.12em;
color: var(--accent);
font-style: normal;
}
.verse-ref {
margin: 1.6rem 0 0;
font-family: var(--display);
letter-spacing: 0.18em;
text-indent: 0.18em;
color: var(--accent);
font-size: 0.9rem;
}
@keyframes rise {
from {
opacity: 0;
transform: translateY(14px);
filter: blur(6px);
}
to {
opacity: 1;
transform: none;
filter: none;
}
}
.app-footer {
width: 100%;
}
.countdown {
margin: 0;
font-size: 0.85rem;
color: var(--ink-dim);
letter-spacing: 0.06em;
}
.countdown strong {
color: var(--accent-soft);
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.foot-note {
margin: 0.6rem 0 0;
font-size: 0.7rem;
letter-spacing: 0.18em;
text-indent: 0.18em;
text-transform: uppercase;
color: var(--ink-faint);
}
@media (prefers-reduced-motion: reduce) {
.seek-btn,
.verse {
animation: none;
}
}
+69
View File
@@ -0,0 +1,69 @@
export interface KJVVerseData {
verse: number
text: string
}
export interface KJVChapterData {
chapter: string
verses: KJVVerseData[]
}
export interface KJVBookData {
book: string
chapters: KJVChapterData[]
}
export interface KJVData {
books: KJVBookData[]
}
export interface Verse {
text: string
reference: string
}
let flat: Verse[] | null = null
export function daySeed(now: Date = new Date()): number {
return Math.floor(now.getTime() / 86_400_000)
}
export function msUntilMidnightUtc(now: Date = new Date()): number {
const next = new Date(now)
next.setUTCHours(24, 0, 0, 0)
return next.getTime() - now.getTime()
}
function mulberry32(seed: number): () => number {
let a = seed >>> 0
return () => {
a = (a + 0x6d2b79f5) | 0
let t = Math.imul(a ^ (a >>> 15), 1 | a)
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
export function pickForDay(verses: Verse[], seed: number): Verse {
const rand = mulberry32(seed)
const index = Math.floor(rand() * verses.length)
return verses[index]
}
export async function loadKJV(): Promise<Verse[]> {
if (flat) return flat
const res = await fetch('/kjv.json')
if (!res.ok) throw new Error(`Failed to load scripture (${res.status})`)
const data = (await res.json()) as KJVData
flat = []
for (const book of data.books) {
const name = book.book === 'Psalms' ? 'Psalm' : book.book
for (const chapter of book.chapters) {
const chapterNumber = Number(chapter.chapter)
for (const v of chapter.verses) {
flat.push({ text: v.text, reference: `${name} ${chapterNumber}:${v.verse}` })
}
}
}
return flat
}
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es2023",
"module": "esnext",
"lib": ["ES2023", "DOM"],
"jsx": "react-jsx",
"strict": true,
"types": ["vite/client"],
"allowArbitraryExtensions": true,
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
+6
View File
@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
})