feat: build Spanish Revival pages and prep Cloudflare

This commit is contained in:
amari is super swag 2025-11-13 22:15:28 -07:00
parent 13f797395c
commit 4262aff530
44 changed files with 1205 additions and 135 deletions

View File

@ -1,7 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
output: "standalone",
};
export default nextConfig;
@ -9,4 +9,7 @@ export default nextConfig;
// Enable calling `getCloudflareContext()` in `next dev`.
// See https://opennext.js.org/cloudflare/bindings#local-access-to-bindings.
import { initOpenNextCloudflareForDev } from "@opennextjs/cloudflare";
initOpenNextCloudflareForDev();
if (process.env.NODE_ENV === "development") {
initOpenNextCloudflareForDev();
}

104
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.1.0",
"dependencies": {
"@opennextjs/cloudflare": "^1.11.0",
"framer-motion": "^12.23.24",
"next": "15.5.6",
"react": "19.1.0",
"react-dom": "19.1.0"
@ -7560,7 +7561,6 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz",
"integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==",
"dev": true,
"license": "MIT OR Apache-2.0",
"dependencies": {
"mime": "^3.0.0"
@ -7573,7 +7573,6 @@
"version": "2.7.10",
"resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.10.tgz",
"integrity": "sha512-mvsNAiJSduC/9yxv1ZpCxwgAXgcuoDvkl8yaHjxoLpFxXy2ugc6TZK20EKgv4yO0vZhAEKwqJm+eGOzf8Oc45w==",
"dev": true,
"license": "MIT OR Apache-2.0",
"peerDependencies": {
"unenv": "2.0.0-rc.24",
@ -7592,7 +7591,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -7609,7 +7607,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -7626,7 +7623,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -7643,7 +7639,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -7660,7 +7655,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -7674,7 +7668,6 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
@ -7687,7 +7680,6 @@
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
@ -9368,7 +9360,6 @@
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz",
"integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==",
"dev": true,
"license": "MIT",
"dependencies": {
"kleur": "^4.1.5"
@ -9378,7 +9369,6 @@
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz",
"integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@poppinss/colors": "^4.1.5",
@ -9390,7 +9380,6 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz",
"integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==",
"dev": true,
"license": "MIT"
},
"node_modules/@rtsao/scc": {
@ -9411,7 +9400,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz",
"integrity": "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@ -10829,7 +10817,6 @@
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.12.tgz",
"integrity": "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==",
"dev": true,
"license": "CC0-1.0"
},
"node_modules/@swc/helpers": {
@ -11806,7 +11793,6 @@
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
@ -12132,7 +12118,6 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
"integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
"dev": true,
"license": "MIT"
},
"node_modules/body-parser": {
@ -12426,7 +12411,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
@ -12458,7 +12442,6 @@
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
@ -12717,7 +12700,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@ -12843,7 +12825,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
"integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
@ -13606,7 +13587,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
"integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -13980,6 +13960,33 @@
"node": ">= 0.6"
}
},
"node_modules/framer-motion": {
"version": "12.23.24",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fresh": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
@ -13999,7 +14006,6 @@
"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,
@ -14201,7 +14207,6 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/glob/node_modules/minimatch": {
@ -14515,7 +14520,6 @@
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"dev": true,
"license": "MIT"
},
"node_modules/is-async-function": {
@ -15072,7 +15076,6 @@
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -15514,7 +15517,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"dev": true,
"license": "MIT",
"bin": {
"mime": "cli.js"
@ -15557,7 +15559,6 @@
"version": "4.20251109.1",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251109.1.tgz",
"integrity": "sha512-btcTw1pH40PGVMwn1pZDcrodQkgY8ijKJA/r7LKgJQGqVZ1k9gqfHHtbelZp8O9bJ995eQqdURyvXMflZwCo+g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@cspotcode/source-map-support": "0.8.1",
@ -15587,7 +15588,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15610,7 +15610,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15633,7 +15632,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15650,7 +15648,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15667,7 +15664,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15684,7 +15680,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15701,7 +15696,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15718,7 +15712,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15735,7 +15728,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15752,7 +15744,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15769,7 +15760,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15792,7 +15782,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15815,7 +15804,6 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15838,7 +15826,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15861,7 +15848,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15884,7 +15870,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@ -15907,7 +15892,6 @@
"cpu": [
"wasm32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
"optional": true,
"dependencies": {
@ -15927,7 +15911,6 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15947,7 +15930,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "Apache-2.0 AND LGPL-3.0-or-later",
"optional": true,
"os": [
@ -15964,7 +15946,6 @@
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@ -15977,7 +15958,6 @@
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@ -16066,6 +16046,21 @@
"obliterator": "^1.6.1"
}
},
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@ -16584,7 +16579,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
@ -17063,7 +17057,6 @@
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"devOptional": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@ -17312,7 +17305,6 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
@ -17380,7 +17372,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4",
@ -17661,7 +17652,6 @@
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
@ -17953,7 +17943,6 @@
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz",
"integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20.18.1"
@ -17969,7 +17958,6 @@
"version": "2.0.0-rc.24",
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
"integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"pathe": "^2.0.3"
@ -18209,7 +18197,6 @@
"version": "1.20251109.0",
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20251109.0.tgz",
"integrity": "sha512-VfazMiymlzos0c1t9AhNi0w8gN9+ZbCVLdEE0VDOsI22WYa6yj+pYOhpZzI/mOzCGmk/o1eNjLMkfjWli6aRVg==",
"dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"bin": {
@ -18230,7 +18217,6 @@
"version": "4.48.0",
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.48.0.tgz",
"integrity": "sha512-qkcwysx96XNDWXl4w/5VjAZjqWatxAq9chMXVeqv/etL9e06ouPaZ+Hwwbe5XYV2GYf/XhZVZ3fHJcTBrq60gQ==",
"dev": true,
"license": "MIT OR Apache-2.0",
"dependencies": {
"@cloudflare/kv-asset-handler": "0.4.0",
@ -18368,7 +18354,6 @@
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@ -18500,7 +18485,6 @@
"version": "4.1.0-beta.10",
"resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz",
"integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@poppinss/colors": "^4.1.5",
@ -18514,7 +18498,6 @@
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz",
"integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@poppinss/exception": "^1.2.2",
@ -18525,7 +18508,6 @@
"version": "3.22.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"

View File

@ -14,6 +14,7 @@
},
"dependencies": {
"@opennextjs/cloudflare": "^1.11.0",
"framer-motion": "^12.23.24",
"next": "15.5.6",
"react": "19.1.0",
"react-dom": "19.1.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

114
src/app/cats/page.tsx Normal file
View File

@ -0,0 +1,114 @@
import Image from "next/image";
import { StudioNav } from "@/components/ui/studio-nav";
import { cats, memorialDetails } from "@/data/cats";
export default function CatsPage() {
return (
<main className="bg-parchment/80 text-coffee">
<div className="mx-auto flex max-w-6xl flex-col gap-12 px-6 py-12 sm:px-10 lg:px-0">
<header className="rounded-[36px] border border-brass/50 bg-stucco/85 px-6 py-8 shadow-[0_25px_80px_rgba(148,74,39,0.2)] sm:px-10">
<StudioNav className="border-b border-brass/40 pb-6" />
<div className="grid gap-8 pt-8 lg:grid-cols-[3fr_2fr]">
<section className="space-y-5">
<p className="text-sm uppercase tracking-[0.3em] text-brass">Cat Universe</p>
<h1 className="font-display text-4xl text-coffee">Portraits, memorials, and mood sliders.</h1>
<p className="text-coffee/90">
Landing grid showcases Miso Soup, Honey-Boy, Coffee June-Bean, and Mochi with arched frames.
</p>
</section>
<section className="rounded-[28px] border border-brass/50 bg-parchment/85 p-6">
<p className="font-display text-xl text-coffee">Page Anatomy</p>
<ul className="mt-6 space-y-4 text-sm text-coffee/90">
<li>Profile cards with brass borders + cat badges.</li>
<li>Individual pages will host gallery, timeline, and letter box.</li>
<li>Mochi&rsquo;s memorial adds sparkles + music toggle.</li>
</ul>
</section>
</div>
</header>
<section className="grid gap-6 rounded-[40px] border border-brass/40 bg-stucco/70 p-6 shadow-[0_30px_90px_rgba(26,26,26,0.1)] md:grid-cols-2">
{cats.map((cat) => (
<article
key={cat.name}
className={`rounded-[30px] border border-brass/60 bg-parchment p-0 ${
cat.isMemorial ? "shadow-[0_18px_60px_rgba(148,74,39,0.2)]" : "shadow-[0_12px_40px_rgba(26,26,26,0.08)]"
}`}
>
<div className="relative h-64 overflow-hidden rounded-t-[30px] border-b border-brass/40">
<Image
src={cat.gallery[0]}
alt={`${cat.name} portrait`}
fill
className="object-cover"
sizes="(min-width: 1024px) 50vw, 100vw"
priority={cat.name === "Miso Soup"}
/>
<div className="pointer-events-none absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-[rgba(37,23,8,0.45)]" />
<p className="absolute left-6 top-6 rounded-full border border-white/50 bg-white/20 px-3 py-1 text-xs uppercase tracking-[0.3em] text-white">
{cat.role}
</p>
</div>
<div className="p-6">
<h2 className="font-display text-3xl text-coffee">{cat.name}</h2>
<p className="mt-2 text-sm text-coffee/80">{cat.description}</p>
<ul className="mt-4 space-y-1 text-xs text-coffee/70">
{cat.favorites.map((fav) => (
<li key={fav}> {fav}</li>
))}
</ul>
<div className="mt-4 inline-flex items-center rounded-full border border-brass/50 px-3 py-1 text-xs uppercase tracking-[0.3em] text-terracotta">
Mood: {cat.mood}
</div>
<div className="mt-6 grid grid-cols-3 gap-2">
{cat.gallery.slice(1, 4).map((src) => (
<div key={src} className="relative h-20 overflow-hidden rounded-2xl border border-brass/40">
<Image src={src} alt={`${cat.name} snapshot`} fill className="object-cover" sizes="(min-width: 1024px) 12vw, 33vw" />
</div>
))}
</div>
{cat.isMemorial && (
<p className="mt-4 text-xs uppercase tracking-[0.3em] text-coffee/70">Memorial mode enabled</p>
)}
</div>
</article>
))}
</section>
<section className="grid gap-8 lg:grid-cols-[3fr_2fr]">
<div className="rounded-[34px] border border-brass/50 bg-terracotta/85 p-8 text-parchment shadow-[0_25px_80px_rgba(148,74,39,0.3)]">
<h2 className="font-display text-3xl">Mochi Memorial</h2>
<p className="mt-4 text-sm">{memorialDetails.note}</p>
<p className="mt-4 text-sm">{memorialDetails.music}</p>
<p className="mt-4 text-sm">{memorialDetails.animation}</p>
<p className="mt-4 text-xs uppercase tracking-[0.3em] text-parchment/80">Letter box saves locally for privacy.</p>
</div>
<div className="rounded-[34px] border border-brass/50 bg-parchment/90 p-8">
<h2 className="font-display text-2xl text-coffee">Gallery Highlights</h2>
<div className="mt-4 grid gap-3 sm:grid-cols-2">
{cats
.filter((cat) => cat.gallery.length > 2)
.map((cat) => (
<div key={cat.name} className="space-y-2">
<p className="font-display text-xl text-terracotta">{cat.name}</p>
<div className="relative h-28 overflow-hidden rounded-2xl border border-brass/40">
<Image
src={cat.gallery[cat.gallery.length - 1] ?? cat.gallery[0]}
alt={`${cat.name} highlight`}
fill
className="object-cover"
sizes="(min-width:1024px)25vw,50vw"
/>
</div>
</div>
))}
</div>
<ul className="mt-4 space-y-2 text-sm text-coffee/80">
<li> Photo gallery with filmstrip slider</li>
<li> Mood slider (sleepy chaotic)</li>
<li> Toy + snack checklist tied to the pantry inventory</li>
</ul>
</div>
</section>
</div>
</main>
);
}

56
src/app/contact/page.tsx Normal file
View File

@ -0,0 +1,56 @@
import { StudioNav } from "@/components/ui/studio-nav";
export default function ContactPage() {
return (
<main className="bg-parchment/80 text-coffee">
<div className="mx-auto flex max-w-4xl flex-col gap-12 px-6 py-12 sm:px-10 lg:px-0">
<header className="rounded-[36px] border border-brass/50 bg-stucco/85 px-6 py-8 shadow-[0_25px_80px_rgba(148,74,39,0.2)] sm:px-10">
<StudioNav className="border-b border-brass/40 pb-6" />
<div className="pt-8">
<p className="text-sm uppercase tracking-[0.3em] text-brass">Contact</p>
<h1 className="mt-2 font-display text-4xl text-coffee">Book, collaborate, or drop a love note.</h1>
<p className="mt-4 text-coffee/90">
Form styling mirrors the UI inspiration boardcream inputs, coffee borders, and terracotta submit button.
</p>
</div>
</header>
<section className="grid gap-8 rounded-[34px] border border-brass/40 bg-stucco/70 p-6 shadow-[0_30px_90px_rgba(26,26,26,0.1)] lg:grid-cols-2">
<form className="space-y-4">
<label className="block text-sm">
<span className="text-xs uppercase tracking-[0.3em] text-brass">Name</span>
<input className="mt-1 w-full rounded-2xl border border-coffee/40 bg-parchment px-4 py-3 font-ui" placeholder="Amari" />
</label>
<label className="block text-sm">
<span className="text-xs uppercase tracking-[0.3em] text-brass">Email</span>
<input className="mt-1 w-full rounded-2xl border border-coffee/40 bg-parchment px-4 py-3 font-ui" placeholder="hola@grimmtattoo.com" />
</label>
<label className="block text-sm">
<span className="text-xs uppercase tracking-[0.3em] text-brass">Message</span>
<textarea className="mt-1 h-32 w-full rounded-2xl border border-coffee/40 bg-parchment px-4 py-3 font-ui" placeholder="Tell me about the flash, music, or recipe you loved." />
</label>
<button type="button" className="w-full rounded-full bg-terracotta px-6 py-3 font-ui text-sm uppercase tracking-[0.3em] text-stucco shadow-[0_12px_35px_rgba(196,106,74,0.35)]">
Send Message
</button>
<p className="text-center text-xs uppercase tracking-[0.3em] text-coffee/70">Form posts to Cloudflare Worker (todo)</p>
</form>
<div className="space-y-6">
<div className="rounded-[24px] border border-brass/50 bg-parchment p-5">
<p className="font-display text-2xl text-coffee">Studio Hours</p>
<ul className="mt-3 text-sm text-coffee/80">
<li>WedFri: 11a 7p</li>
<li>Sat: 10a 4p</li>
<li>SunTue: design + admin</li>
</ul>
</div>
<div className="rounded-[24px] border border-brass/50 bg-terracotta/85 p-5 text-parchment">
<p className="font-display text-2xl">Casa Azul</p>
<p className="mt-2 text-sm">
Hidden page for the secret love letter. Password field + fade-in animation launching soon.
</p>
</div>
</div>
</section>
</div>
</main>
);
}

View File

@ -0,0 +1,98 @@
import { StudioNav } from "@/components/ui/studio-nav";
import { flashPieces } from "@/data/flash";
const vaultNotes = [
{
title: "Hover Wiggle",
text: "Frames lean 3° with a terracotta drop shadow to mimic gallery curiosity.",
},
{
title: "Modal Story",
text: "Detail views reveal pricing, size, and stories with Fraunces headings + Crimson body text.",
},
{
title: "Book Button",
text: "Olive button with brass border launches GlossGenius for deposits.",
},
];
const detailFlow = [
"Enter /flash and browse a brass-framed grid.",
"Hover to preview availability aura (terracotta = bookable, olive = waitlist).",
"Click a frame to open the modal with tags, pricing, and related sets.",
"Save favorites to a future admin dashboard or export as PDF for clients.",
];
export default function FlashVaultPage() {
return (
<main className="bg-parchment/80 text-coffee">
<div className="mx-auto flex max-w-6xl flex-col gap-12 px-6 py-12 sm:px-10 lg:px-0">
<header className="rounded-[36px] border border-brass/50 bg-stucco/90 px-6 py-8 shadow-[0_25px_80px_rgba(148,74,39,0.2)] sm:px-10">
<StudioNav className="border-b border-brass/40 pb-6" />
<div className="grid gap-8 pt-8 lg:grid-cols-[3fr_2fr]">
<section className="space-y-5">
<p className="text-sm uppercase tracking-[0.3em] text-brass">Flash Vault</p>
<h1 className="font-display text-4xl text-coffee">Framed flash stories with brass-lined intent.</h1>
<p className="text-coffee/90">
Each tile recreates the inspiration board&rsquo;s ornate frames. Hover states wiggle, and modals retell the origin of
every set.
</p>
<div className="flex flex-wrap gap-3">
<span className="rounded-full border border-brass/60 px-4 py-1 text-xs uppercase tracking-[0.3em]">Grid Modal Checkout</span>
<span className="rounded-full border border-brass/60 px-4 py-1 text-xs uppercase tracking-[0.3em]">Autosave Favorites</span>
</div>
</section>
<section className="rounded-[28px] border border-brass/50 bg-parchment/80 p-6">
<p className="font-display text-xl text-coffee">Microinteractions</p>
<ul className="mt-6 space-y-4 text-sm text-coffee/90">
{vaultNotes.map((note) => (
<li key={note.title} className="rounded-2xl border border-brass/40 bg-white/60 p-4">
<p className="font-display text-lg text-terracotta">{note.title}</p>
<p>{note.text}</p>
</li>
))}
</ul>
</section>
</div>
</header>
<section className="grid gap-6 rounded-[40px] border border-brass/40 bg-stucco/70 p-6 shadow-[0_30px_90px_rgba(26,26,26,0.1)] md:grid-cols-2 lg:grid-cols-3">
{flashPieces.map((piece) => (
<article key={piece.name} className="rounded-[28px] border border-brass/60 bg-parchment p-5 shadow-[0_18px_50px_rgba(55,30,7,0.15)]">
<div className={`mb-4 h-36 rounded-[24px] border border-iron/10 bg-gradient-to-br ${piece.gradient} shadow-[inset_0_2px_10px_rgba(255,255,255,0.4)]`} aria-hidden />
<p className="text-xs uppercase tracking-[0.3em] text-brass">{piece.theme}</p>
<h2 className="mt-2 font-display text-2xl text-coffee">{piece.name}</h2>
<p className="mt-2 text-sm text-coffee/90">{piece.story}</p>
<div className="mt-4 flex flex-wrap gap-2 text-xs text-coffee/80">
{piece.tags.map((tag) => (
<span key={tag} className="rounded-full border border-brass/50 px-3 py-1 uppercase tracking-[0.2em]">
{tag}
</span>
))}
</div>
<div className="mt-4 flex items-center justify-between text-sm">
<span>{piece.size} {piece.price}</span>
<span className={`font-ui uppercase tracking-[0.3em] ${piece.availability === "bookable" ? "text-terracotta" : piece.availability === "waitlist" ? "text-olive" : "text-coffee/70"}`}>
{piece.availability}
</span>
</div>
<p className="mt-3 text-sm text-coffee/80">{piece.detail}</p>
<button className="mt-5 inline-flex items-center gap-2 rounded-full border border-olive/50 px-4 py-2 text-xs uppercase tracking-[0.3em] text-olive">
Book / Inquire
<span aria-hidden></span>
</button>
</article>
))}
</section>
<section className="rounded-[40px] border border-brass/40 bg-terracotta/85 p-8 text-parchment shadow-[0_35px_90px_rgba(148,74,39,0.35)]">
<h2 className="font-display text-3xl">Detail Flow</h2>
<ol className="mt-6 list-decimal space-y-4 pl-6 text-sm">
{detailFlow.map((step) => (
<li key={step}>{step}</li>
))}
</ol>
<p className="mt-6 text-xs uppercase tracking-[0.3em] text-parchment/80">Future: admin uploads, availability toggles, related suggestions.</p>
</section>
</div>
</main>
);
}

View File

@ -1,26 +1,43 @@
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
--color-stucco: #f6efe4;
--color-terracotta: #c46a4a;
--color-olive: #7d8e6a;
--color-coffee: #4a3628;
--color-iron: #1a1a1a;
--color-brass: #b08a57;
--color-adobe: #9a3e2f;
--color-sage: #c3d0ba;
--color-parchment: #fdf8f1;
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
--color-background: var(--color-stucco);
--color-foreground: var(--color-iron);
--color-stucco: var(--color-stucco);
--color-terracotta: var(--color-terracotta);
--color-olive: var(--color-olive);
--color-coffee: var(--color-coffee);
--color-iron: var(--color-iron);
--color-brass: var(--color-brass);
--color-adobe: var(--color-adobe);
--color-sage: var(--color-sage);
--color-parchment: var(--color-parchment);
--font-display: var(--font-display);
--font-body: var(--font-body);
--font-ui: var(--font-ui);
}
body {
background: var(--background);
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
background-color: var(--color-stucco);
background-image: radial-gradient(circle at top, rgba(255, 255, 255, 0.55), transparent 55%),
linear-gradient(135deg, rgba(196, 106, 74, 0.15), transparent 45%);
color: var(--color-iron);
font-family: var(--font-body), serif;
min-height: 100vh;
}
* {
box-sizing: border-box;
}

View File

@ -1,20 +1,28 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Crimson_Text, Fraunces, Inter } from "next/font/google";
import "./globals.css";
const geistSans = Geist({
variable: "--font-geist-sans",
const fraunces = Fraunces({
variable: "--font-display",
weight: ["400", "500", "600", "700"],
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
const crimson = Crimson_Text({
variable: "--font-body",
weight: ["400", "600", "700"],
subsets: ["latin"],
});
const inter = Inter({
variable: "--font-ui",
weight: ["400", "500", "600", "700"],
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "GrimmTattoo — Spanish Revival",
description: "A warm Spanish Revival-inspired creative hub for flash, music, recipes, and memories.",
};
export default function RootLayout({
@ -27,7 +35,7 @@ export default function RootLayout({
<head>
<link rel="icon" href="/favicon.svg" type="image/svg+xml"></link>
</head>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
<body className={`${fraunces.variable} ${crimson.variable} ${inter.variable} antialiased`}>{children}</body>
</html>
);
}

94
src/app/music/page.tsx Normal file
View File

@ -0,0 +1,94 @@
import { StudioNav } from "@/components/ui/studio-nav";
import { playlists, audioNotes } from "@/data/music";
export default function MusicPage() {
return (
<main className="bg-parchment/80 text-coffee">
<div className="mx-auto flex max-w-6xl flex-col gap-12 px-6 py-12 sm:px-10 lg:px-0">
<header className="rounded-[36px] border border-brass/50 bg-stucco/85 px-6 py-8 shadow-[0_25px_80px_rgba(148,74,39,0.2)] sm:px-10">
<StudioNav className="border-b border-brass/40 pb-6" />
<div className="grid gap-8 pt-8 lg:grid-cols-[3fr_2fr]">
<section className="space-y-5">
<p className="text-sm uppercase tracking-[0.3em] text-brass">Music Corner</p>
<h1 className="font-display text-4xl text-coffee">Playlists, cassette vibes, and recorded whispers.</h1>
<p className="text-coffee/90">
Album cards echo the UI inspiration board&rsquo;s stacked layoutscomplete with embedded players and captions.
</p>
<div className="flex flex-wrap gap-3 text-xs uppercase tracking-[0.3em]">
<span className="rounded-full border border-brass/60 px-4 py-1">Spotify Embed</span>
<span className="rounded-full border border-brass/60 px-4 py-1">Audio Uploads</span>
</div>
</section>
<section className="rounded-[28px] border border-brass/50 bg-parchment/85 p-6">
<p className="font-display text-xl text-coffee">Studio Soundtrack Guide</p>
<ul className="mt-6 space-y-4 text-sm text-coffee/90">
<li>Morning: nylon strings + birds for flash sketching.</li>
<li>Afternoon: percussion loops to push outline energy.</li>
<li>Evening: vinyl R&B and lullabies in the cat memorial nook.</li>
</ul>
</section>
</div>
</header>
<section className="rounded-[40px] border border-brass/40 bg-stucco/70 p-6 shadow-[0_30px_90px_rgba(26,26,26,0.1)]">
<div className="grid gap-6 xl:grid-cols-2">
{playlists.map((playlist) => (
<article key={playlist.title} className="rounded-[30px] border border-brass/60 bg-parchment p-6 shadow-[0_18px_50px_rgba(55,30,7,0.12)]">
<div className={`h-48 rounded-[24px] border border-iron/10 bg-gradient-to-br ${playlist.color} p-4 text-parchment`}>
<p className="text-xs uppercase tracking-[0.3em]">{playlist.mood}</p>
<p className="mt-6 font-display text-2xl">{playlist.title}</p>
<p className="mt-2 text-sm">{playlist.length}</p>
</div>
<p className="mt-4 text-sm text-coffee/90">{playlist.caption}</p>
<ul className="mt-4 space-y-1 text-xs text-coffee/70">
{playlist.tracks.map((track) => (
<li key={track}> {track}</li>
))}
</ul>
<div className="mt-5 overflow-hidden rounded-[24px] border border-brass/40">
<iframe
title={`${playlist.title} Spotify embed`}
className="h-[300px] w-full"
src={playlist.embedUrl}
allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
loading="lazy"
allowFullScreen
style={{ border: "none" }}
/>
</div>
<button className="mt-5 inline-flex items-center gap-2 rounded-full border border-brass px-4 py-2 text-xs uppercase tracking-[0.3em] text-coffee">
Open Playlist
<span aria-hidden></span>
</button>
</article>
))}
</div>
</section>
<section className="grid gap-8 lg:grid-cols-[3fr_2fr]">
<div className="rounded-[34px] border border-brass/50 bg-terracotta/85 p-8 text-parchment shadow-[0_25px_80px_rgba(148,74,39,0.3)]">
<h2 className="font-display text-3xl">Personal Audio Uploads</h2>
<p className="mt-4 text-sm">
Drop WAV or MP3 files directly. UI mirrors a cassette deck with brass dials and terracotta buttons per the PRD.
</p>
<ul className="mt-6 space-y-4">
{audioNotes.map((memo) => (
<li key={memo.title} className="rounded-2xl border border-parchment/40 bg-parchment/15 p-4">
<p className="font-display text-xl">{memo.title}</p>
<p className="text-sm">{memo.detail}</p>
<p className="mt-2 text-xs uppercase tracking-[0.3em] text-parchment/70">{memo.duration}</p>
</li>
))}
</ul>
</div>
<div className="rounded-[34px] border border-brass/50 bg-parchment/90 p-8">
<h2 className="font-display text-2xl text-coffee">Embeds & Accessibility</h2>
<ul className="mt-4 space-y-3 text-sm text-coffee/80">
<li>Spotify iframe theme recolored with CSS filters to stay on-brand.</li>
<li>Keyboard controls for previous/next track buttons.</li>
<li>Optional captions describing mood + instrumentation.</li>
</ul>
</div>
</section>
</div>
</main>
);
}

View File

@ -1,52 +1,151 @@
import Image from "next/image";
import Link from "next/link";
import { StudioNav } from "@/components/ui/studio-nav";
const featureCards = [
{
title: "Virtual Scrapbook",
description: "Drag, drop, pin, and noodle. A parchment canvas auto-saves every layer, sticker, and story.",
meta: "Canvas • Autosave",
accent: "from-terracotta/90 via-adobe/80 to-brass/80",
},
{
title: "Flash Vault",
description: "Curate tattoo flash by mood or motif. Brass hover borders hint at availability and drop-ins.",
meta: "Gallery • Filters",
accent: "from-olive/80 via-sage/70 to-stucco/90",
},
{
title: "Music Corner",
description: "Spotify embeds and cassette-style playlists soundtrack long studio nights.",
meta: "Playlists • Audio",
accent: "from-coffee/90 via-terracotta/80 to-olive/80",
},
{
title: "Digital Pantry",
description: "Recipes, prep lists, and a playful spinner wheel to decide the next comfort meal.",
meta: "Recipes • Inventory",
accent: "from-brass/90 via-parchment/80 to-terracotta/90",
},
{
title: "Cat Universe",
description: "Profiles, memorial notes, and photo galleries honoring every whiskered roommate.",
meta: "Profiles • Galleries",
accent: "from-sage/80 via-olive/80 to-coffee/90",
},
{
title: "Secret Love Letter",
description: "A hidden Casa Azul URL with handwritten overlays, voice memo, and soft fade-ins.",
meta: "Hidden • Animated",
accent: "from-adobe/90 via-terracotta/80 to-coffee/90",
},
];
export default function Home() {
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image className="dark:invert" src="/next.svg" alt="Next.js logo" width={180} height={38} priority />
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">Save and see your changes instantly.</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
<main className="bg-[#a34f33] text-coffee">
<div className="mx-auto flex max-w-6xl flex-col gap-12 px-4 pb-16 pt-12 sm:px-8">
<StudioNav variant="dark" />
<div className="grid gap-6 lg:grid-cols-12 auto-rows-[minmax(120px,_auto)]">
<section className="relative rounded-[32px] border border-brass/60 bg-stucco p-8 text-coffee shadow-[0_25px_80px_rgba(64,32,12,0.35)] lg:col-span-7">
<p className="text-sm uppercase tracking-[0.3em] text-brass">Casa Grimm</p>
<h1 className="mt-3 font-display text-4xl text-coffee">Spanish Revival Sanctuary</h1>
<p className="mt-4 text-base text-coffee/90">
A warm landing page for Amari: flash sets, pantry rituals, playlists for late-night lining, and the cat universe all
share one parchment wall inspired by her mood board.
</p>
<div className="mt-6 flex flex-wrap gap-3 text-xs uppercase tracking-[0.3em] text-coffee">
<Link href="/flash-vault" className="rounded-full bg-terracotta px-6 py-3 text-stucco shadow-[0_12px_35px_rgba(146,53,24,0.45)]">
Explore Vault
</Link>
<Link href="/scrapbook" className="rounded-full border border-coffee px-6 py-3 text-coffee">
Visit Scrapbook
</Link>
</div>
<div className="mt-10 flex flex-wrap gap-4 text-sm text-coffee/90">
<div>
<p className="font-display text-3xl text-coffee">24</p>
<p>Flash pieces ready</p>
</div>
<div>
<p className="font-display text-3xl text-coffee">6</p>
<p>Studio corners to explore</p>
</div>
</div>
</section>
<section className="rounded-[32px] border border-brass/70 bg-parchment p-8 text-coffee lg:col-span-5">
<p className="font-display text-2xl text-coffee">Studio Whisper</p>
<div className="mt-4 space-y-4 text-sm text-coffee/90">
<p>Morning cafecito + plant watering before tattooing.</p>
<p>Afternoons are for recipe swaps and pantry planning.</p>
<p>Evenings belong to playlists, journaling, and cuddling the cats.</p>
</div>
</section>
<div className="rounded-[28px] border border-parchment/40 bg-parchment/30 p-6 text-coffee lg:col-span-6">
<p className="font-display text-2xl">Spanish Garden Playlist</p>
<p className="text-sm">Cassette buttons for playlists just like the board.</p>
<div className="mt-4 flex items-center gap-3">
<button className="rounded-full border border-coffee/40 px-4 py-2"></button>
<button className="rounded-full border border-coffee/40 px-6 py-2"></button>
<button className="rounded-full border border-coffee/40 px-4 py-2"></button>
</div>
<p className="mt-4 text-xs uppercase tracking-[0.3em] text-coffee/70">Pairs with /music</p>
</div>
<div className="rounded-[28px] border border-parchment/40 bg-terracotta/70 p-6 text-parchment lg:col-span-3">
<p className="font-display text-2xl">Flash Vault</p>
<p className="mt-2 text-sm">Brass-framed arches waiting for appointments.</p>
<Link className="mt-4 inline-flex items-center gap-2 rounded-full border border-parchment px-4 py-2 text-xs uppercase tracking-[0.3em]" href="/flash-vault">
Enter Vault
</Link>
</div>
<div className="rounded-[28px] border border-parchment/40 bg-[#622f1d] p-6 text-parchment lg:col-span-3">
<p className="font-display text-2xl">Casa Azul Secret</p>
<p className="mt-2 text-sm">Hidden link with handwritten letter + slideshow.</p>
<p className="mt-4 text-xs uppercase tracking-[0.3em] text-parchment/70">Password tucked in the arches.</p>
</div>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image aria-hidden src="/file.svg" alt="File icon" width={16} height={16} />
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image aria-hidden src="/globe.svg" alt="Globe icon" width={16} height={16} />
Go to nextjs.org
</a>
</footer>
</div>
<section className="rounded-[36px] border border-parchment/40 bg-[#7c3c25] p-8 text-parchment shadow-[0_30px_90px_rgba(26,26,26,0.4)]">
<div className="flex flex-col gap-2 text-parchment sm:flex-row sm:items-end sm:justify-between">
<div>
<p className="text-xs uppercase tracking-[0.3em]">Studio Toolkit</p>
<h2 className="font-display text-3xl">Feature Capsules</h2>
</div>
<p className="text-sm text-parchment/80">Every capsule references a card from the inspiration board.</p>
</div>
<div className="mt-6 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{featureCards.map((feature) => (
<article key={feature.title} className="relative overflow-hidden rounded-[28px] border border-brass/50 bg-parchment/80 p-5 text-coffee">
<div className={`absolute inset-x-0 top-0 h-2 bg-gradient-to-r ${feature.accent}`} aria-hidden />
<p className="text-xs font-ui uppercase tracking-[0.3em] text-brass">{feature.meta}</p>
<h2 className="mt-4 font-display text-2xl text-coffee">{feature.title}</h2>
<p className="mt-3 text-sm text-coffee/90">{feature.description}</p>
<button className="mt-6 inline-flex items-center gap-2 text-sm font-ui uppercase tracking-[0.2em] text-terracotta">
View Details
<span aria-hidden></span>
</button>
</article>
))}
</div>
</section>
<section className="rounded-[40px] border border-parchment/40 bg-terracotta/80 p-8 text-parchment shadow-[0_35px_90px_rgba(26,26,26,0.45)]">
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
<div className="max-w-2xl">
<h3 className="font-display text-3xl">Spanish Revival Promise</h3>
<p className="mt-4 text-base">
Every corner should feel like Amaris studio: terracotta warmth, archived stories, cats honored, meals planned, and
flash ready for the next appointment.
</p>
</div>
<div className="rounded-[28px] border border-parchment/40 bg-parchment/15 p-6 text-sm uppercase tracking-[0.3em] text-parchment">
<span className="text-3xl font-display"></span>
<p>Love Notes</p>
<span className="mt-4 block text-xs text-parchment/80">Flash Pantry Cats</span>
</div>
</div>
</section>
<footer className="text-center text-xs uppercase tracking-[0.3em] text-parchment/70">
© {new Date().getFullYear()} GrimmTattoo Casa Azul is still hidden.
</footer>
</div>
</main>
);
}

102
src/app/pantry/page.tsx Normal file
View File

@ -0,0 +1,102 @@
import { StudioNav } from "@/components/ui/studio-nav";
import { groceryList, mealPrep, pantryInventory, recipes, spinnerMeals } from "@/data/pantry";
export default function PantryPage() {
return (
<main className="bg-parchment/85 text-coffee">
<div className="mx-auto flex max-w-6xl flex-col gap-12 px-6 py-12 sm:px-10 lg:px-0">
<header className="rounded-[36px] border border-brass/50 bg-stucco/85 px-6 py-8 shadow-[0_25px_80px_rgba(148,74,39,0.2)] sm:px-10">
<StudioNav className="border-b border-brass/40 pb-6" />
<div className="grid gap-8 pt-8 lg:grid-cols-[3fr_2fr]">
<section className="space-y-5">
<p className="text-sm uppercase tracking-[0.3em] text-brass">Digital Pantry</p>
<h1 className="font-display text-4xl text-coffee">Recipes, meal prep, and a spinner wheel for indecisive nights.</h1>
<p className="text-coffee/90">
Tabs will segment recipes, meal plans, grocery lists, inventory, and the wheel just like the PRD spells out.
</p>
</section>
<section className="rounded-[28px] border border-brass/50 bg-parchment/85 p-6">
<p className="font-display text-xl text-coffee">Quick Toggles</p>
<ul className="mt-6 space-y-3 text-sm text-coffee/90">
<li> Recipes</li>
<li> Meal Prep</li>
<li> Grocery List</li>
<li> Inventory</li>
<li> Spinner</li>
</ul>
</section>
</div>
</header>
<section className="grid gap-6 rounded-[40px] border border-brass/40 bg-stucco/70 p-6 shadow-[0_30px_90px_rgba(26,26,26,0.1)] md:grid-cols-2">
{recipes.map((recipe) => (
<article key={recipe.name} className="rounded-[28px] border border-brass/60 bg-parchment p-6">
<div className={`h-24 rounded-[20px] border border-iron/10 bg-gradient-to-r ${recipe.accent}`} aria-hidden />
<p className="mt-4 text-xs uppercase tracking-[0.3em] text-brass">{recipe.occasion}</p>
<h2 className="mt-2 font-display text-2xl text-coffee">{recipe.name}</h2>
<p className="mt-2 text-sm text-coffee/80">{recipe.description}</p>
<ul className="mt-4 flex flex-wrap gap-2 text-xs text-coffee/70">
{recipe.ingredients.map((ingredient) => (
<li key={ingredient} className="rounded-full border border-brass/50 px-3 py-1 uppercase tracking-[0.2em]">
{ingredient}
</li>
))}
</ul>
</article>
))}
</section>
<section className="grid gap-8 lg:grid-cols-[3fr_2fr]">
<div className="rounded-[34px] border border-brass/50 bg-parchment/90 p-8">
<h2 className="font-display text-2xl text-coffee">Meal Prep Timeline</h2>
<ul className="mt-4 space-y-4 text-sm text-coffee/80">
{mealPrep.map((entry) => (
<li key={entry.day} className="flex items-center justify-between rounded-2xl border border-brass/40 bg-white/60 p-4">
<span className="font-display text-xl text-terracotta">{entry.day}</span>
<span>{entry.plan}</span>
</li>
))}
</ul>
</div>
<div className="rounded-[34px] border border-brass/50 bg-terracotta/85 p-8 text-parchment shadow-[0_25px_80px_rgba(148,74,39,0.3)]">
<h2 className="font-display text-3xl">Grocery Spinner</h2>
<p className="mt-4 text-sm">
Wheel uses SVG segments tinted in terracotta, olive, and brass. Tap to spin and pick the next cozy meal.
</p>
<ul className="mt-4 list-disc space-y-2 pl-6 text-sm">
{spinnerMeals.map((meal) => (
<li key={meal}>{meal}</li>
))}
</ul>
</div>
</section>
<section className="grid gap-8 lg:grid-cols-2">
<div className="rounded-[34px] border border-brass/50 bg-stucco/80 p-8">
<h2 className="font-display text-2xl text-coffee">Grocery List</h2>
<ul className="mt-4 space-y-3 text-sm text-coffee/80">
{groceryList.map((entry) => (
<li key={entry.item} className="flex items-center justify-between rounded-2xl border border-brass/40 bg-white/60 p-4">
<span>{entry.item}</span>
<span className="uppercase tracking-[0.3em] text-terracotta">{entry.status}</span>
</li>
))}
</ul>
</div>
<div className="rounded-[34px] border border-brass/50 bg-parchment/90 p-8">
<h2 className="font-display text-2xl text-coffee">Pantry Inventory</h2>
<div className="mt-4 grid gap-4 sm:grid-cols-2">
{pantryInventory.map((section) => (
<div key={section.category} className="rounded-2xl border border-brass/40 bg-white/60 p-4 text-sm">
<p className="font-display text-xl text-terracotta">{section.category}</p>
<ul className="mt-2 space-y-1">
{section.items.map((item) => (
<li key={item}> {item}</li>
))}
</ul>
</div>
))}
</div>
</div>
</section>
</div>
</main>
);
}

View File

@ -0,0 +1,92 @@
import { StudioNav } from "@/components/ui/studio-nav";
import { scrapbookAssets, scrapbookTools, snapshotIdeas } from "@/data/scrapbook";
export default function ScrapbookPage() {
return (
<main className="bg-parchment/85 text-coffee">
<div className="mx-auto flex max-w-6xl flex-col gap-12 px-6 py-12 sm:px-10 lg:px-0">
<header className="rounded-[36px] border border-brass/50 bg-stucco/90 px-6 py-8 shadow-[0_25px_80px_rgba(148,74,39,0.2)] sm:px-10">
<StudioNav className="border-b border-brass/40 pb-6" />
<div className="grid gap-8 pt-8 lg:grid-cols-[7fr_5fr]">
<section className="space-y-5">
<p className="text-sm uppercase tracking-[0.3em] text-brass">Virtual Scrapbook</p>
<h1 className="font-display text-4xl text-coffee">Drag, drop, rotate, repeat chaos but make it archival.</h1>
<p className="text-coffee/90">
Canvas uses parchment texture, asset drawer lives on the left, and toolbar anchors on the right just like the
PRD calls out.
</p>
<div className="rounded-[28px] border border-brass/40 bg-parchment/80 p-6">
<p className="font-display text-2xl text-coffee">Canvas Snapshot</p>
<p className="mt-2 text-sm text-coffee/80">
Arched viewport, dotted guidelines, and ghosted drop zones for mobile view-only mode.
</p>
<div className="mt-5 grid gap-3 sm:grid-cols-2">
{snapshotIdeas.map((idea) => (
<div key={idea} className="rounded-2xl border border-brass/40 bg-white/60 p-3 text-sm">
{idea}
</div>
))}
</div>
</div>
</section>
<section className="rounded-[28px] border border-brass/50 bg-parchment/85 p-6">
<p className="font-display text-xl text-coffee">Toolbar Actions</p>
<ul className="mt-6 space-y-4 text-sm text-coffee/90">
{scrapbookTools.map((tool) => (
<li key={tool.name} className="rounded-2xl border border-brass/40 bg-white/60 p-4">
<p className="font-display text-lg text-terracotta">{tool.name}</p>
<p>{tool.detail}</p>
</li>
))}
</ul>
</section>
</div>
</header>
<section className="rounded-[40px] border border-brass/40 bg-stucco/70 p-6 shadow-[0_30px_90px_rgba(26,26,26,0.1)]">
<div className="flex flex-col gap-6 lg:flex-row lg:items-start">
<div className="lg:w-1/3">
<p className="font-display text-2xl text-coffee">Asset Sidebar</p>
<p className="mt-2 text-sm text-coffee/80">
Left-docked drawer with tabs for uploads, stickers, textures, swatches, and notes.
</p>
</div>
<div className="grid flex-1 gap-5 sm:grid-cols-2">
{scrapbookAssets.map((asset) => (
<article key={asset.name} className="rounded-[28px] border border-brass/60 bg-parchment p-5 shadow-[0_18px_50px_rgba(55,30,7,0.12)]">
<p className="text-xs uppercase tracking-[0.3em] text-brass">{asset.name}</p>
<p className="mt-2 text-sm text-coffee/90">{asset.description}</p>
<ul className="mt-4 space-y-1 text-xs text-coffee/80">
{asset.items.map((item) => (
<li key={item}> {item}</li>
))}
</ul>
</article>
))}
</div>
</div>
</section>
<section className="grid gap-8 lg:grid-cols-2">
<div className="rounded-[34px] border border-brass/40 bg-terracotta/85 p-8 text-parchment shadow-[0_25px_80px_rgba(148,74,39,0.35)]">
<h2 className="font-display text-3xl">Export & Print</h2>
<p className="mt-4 text-sm">
Autosave each change via localStorage, then trigger PNG/JPG export thanks to canvas serialization. Print layout toggles
adds safe margins for 8.5x11.
</p>
<p className="mt-4 text-xs uppercase tracking-[0.3em] text-parchment/80">Future: multi-notebook + share links.</p>
</div>
<div className="rounded-[34px] border border-brass/50 bg-parchment/90 p-8">
<h2 className="font-display text-2xl text-coffee">Mobile View</h2>
<p className="mt-3 text-sm text-coffee/80">
View-only with ability to rearrange via simplified toolbar, while editing prompts the user to open desktop.
</p>
<ul className="mt-4 list-disc space-y-2 pl-6 text-sm">
<li>Tap to reveal asset details</li>
<li>Quick duplicate + delete actions pinned bottom-right</li>
<li>Subtle vibration feedback when locking layers</li>
</ul>
</div>
</section>
</div>
</main>
);
}

View File

@ -0,0 +1,79 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
const navItems = [
{ label: "Home", href: "/" },
{ label: "Flash Vault", href: "/flash-vault" },
{ label: "Scrapbook", href: "/scrapbook" },
{ label: "Music", href: "/music" },
{ label: "Pantry", href: "/pantry" },
{ label: "Cats", href: "/cats" },
{ label: "Contact", href: "/contact" },
];
type StudioNavProps = {
className?: string;
variant?: "light" | "dark";
};
const themes = {
dark: {
container: "rounded-[40px] border border-parchment/30 bg-gradient-to-r from-[#5f2210] via-[#8c3a1d] to-[#5f2210] px-6 py-6 shadow-[0_12px_35px_rgba(30,10,5,0.45)]",
label: "text-parchment/70",
title: "text-parchment",
list: "text-parchment",
active: "border-parchment bg-parchment text-terracotta shadow-[0_10px_30px_rgba(253,248,241,0.35)]",
inactive: "border-parchment/50 text-parchment/80 hover:border-parchment hover:text-parchment",
underline: "bg-parchment/30",
},
light: {
container: "rounded-[32px] border border-brass/40 bg-parchment/80 px-6 py-6",
label: "text-brass",
title: "text-coffee",
list: "text-coffee",
active: "border-terracotta bg-terracotta text-stucco shadow-[0_8px_30px_rgba(196,106,74,0.35)]",
inactive: "border-brass/60 text-coffee hover:border-terracotta hover:text-terracotta",
underline: "bg-terracotta/30",
},
};
export function StudioNav({ className = "", variant = "light" }: StudioNavProps) {
const pathname = usePathname();
const theme = themes[variant];
const wrapperClasses = ["flex flex-col gap-4", theme.container, className].filter(Boolean).join(" ");
return (
<nav className={wrapperClasses}>
<div>
<p className={`text-sm uppercase tracking-[0.25em] ${theme.label}`}>GrimmTattoo</p>
<p className={`font-display text-3xl ${theme.title}`}>Spanish Revival Studio</p>
</div>
<div>
<ul className={`flex flex-wrap gap-3 text-sm font-ui uppercase tracking-wide ${theme.list}`}>
{navItems.map((item) => {
const isActive = pathname === item.href || (pathname?.startsWith(item.href) && item.href !== "/");
const activeClasses = theme.active;
const inactiveClasses = theme.inactive;
return (
<li key={item.label}>
<Link
href={item.href}
className={`inline-flex items-center rounded-full border px-4 py-1 text-xs sm:text-sm transition-colors ${
isActive ? activeClasses : inactiveClasses
}`}
aria-current={isActive ? "page" : undefined}
>
{item.label}
</Link>
</li>
);
})}
</ul>
<div className={`mt-3 h-px w-full rounded-full ${theme.underline}`} />
</div>
</nav>
);
}

67
src/data/cats.ts Normal file
View File

@ -0,0 +1,67 @@
export type CatProfile = {
name: string;
role: string;
description: string;
favorites: string[];
mood: string;
isMemorial?: boolean;
color: string;
gallery: string[];
};
export const cats: CatProfile[] = [
{
name: "Miso Soup",
role: "Studio greeter",
description: "Chatty tuxedo who inspects every flash page and naps on the iPad.",
favorites: ["sunbeams", "leftover salmon", "terracotta planters"],
mood: "Curious",
color: "from-olive via-sage to-parchment",
gallery: ["/cats/miso/IMG_0457.jpeg", "/cats/miso/IMG_1056.jpeg", "/cats/mochi-and-miso/IMG_0327.jpeg"],
},
{
name: "Honey-Boy",
role: "Light technician",
description: "Ginger fluff adjusting blinds for the perfect reference light.",
favorites: ["braided cords", "floor cushions", "palm leaves"],
mood: "Soft",
color: "from-terracotta via-adobe to-brass",
gallery: ["/cats/honey-boy/IMG_0988.jpeg", "/cats/mochi-and-miso/IMG_8850.jpeg"],
},
{
name: "Coffee June-Bean",
role: "Security",
description: "Keeps the pantry closed and sits beside every open flame reference.",
favorites: ["espresso foam", "window perches", "linen scraps"],
mood: "Watchful",
color: "from-coffee via-iron to-terracotta",
gallery: [
"/cats/coffee/IMG_0339.jpeg",
"/cats/coffee/IMG_1153.jpeg",
"/cats/coffee/IMG_1730.jpeg",
"/cats/coffee/IMG_5373.jpeg",
],
},
{
name: "Mochi",
role: "Forever muse",
description: "Angel cat memorialized with lantern sparkles and letters.",
favorites: ["gentle brush", "lavender sachets", "voice memos"],
mood: "Stardust",
isMemorial: true,
color: "from-parchment via-brass to-terracotta",
gallery: [
"/cats/mochi/775A306A-399B-4F9D-8FB8-7447821C0008.jpeg",
"/cats/mochi/IMG_0074.jpeg",
"/cats/mochi/IMG_0075.jpeg",
"/cats/mochi/IMG_0585.jpeg",
"/cats/mochi/IMG_9809.jpeg",
],
},
];
export const memorialDetails = {
note: "Write a letter to Mochi, seal it in parchment, and save locally for heart resets.",
music: "Optional lullaby toggle that fades in harp and soft record dust.",
animation: "Low-opacity sparkles drifting upward to mimic dust in a sunbeam.",
};

80
src/data/flash.ts Normal file
View File

@ -0,0 +1,80 @@
export type FlashPiece = {
name: string;
theme: string;
story: string;
tags: string[];
size: string;
price: string;
availability: "bookable" | "waitlist" | "archived";
gradient: string;
detail: string;
};
export const flashPieces: FlashPiece[] = [
{
name: "Courtyard Guardians",
theme: "Architectural",
story: "Twin arches, prayer candles, and creeping jasmine inspired by Granada balconies.",
tags: ["linework", "color", "large"],
size: "7in",
price: "$450",
availability: "bookable",
gradient: "from-terracotta via-adobe to-coffee",
detail: "Looks incredible on thighs, ribs, and forearms. Includes brass border flourishes when healed.",
},
{
name: "Garden Relics",
theme: "Botanical",
story: "A trio of clay pots holding monsteras, citrus, and wild rosemary snipped from abuela's patio.",
tags: ["botanical", "repeatable"],
size: "4in",
price: "$260",
availability: "bookable",
gradient: "from-olive via-sage to-parchment",
detail: "Pairs nicely with micro lettering or a talismanic border.",
},
{
name: "Lantern Novena",
theme: "Sacred light",
story: "A wrought-iron lantern glowing with warm brass glass, referencing Spanish Revival sconces.",
tags: ["lantern", "glow", "medium"],
size: "5in",
price: "$320",
availability: "bookable",
gradient: "from-brass via-terracotta to-coffee",
detail: "Animated hover will mimic flicker in the final UI.",
},
{
name: "Sunspill Oracle",
theme: "Mystic",
story: "Palmistry hands with citrus slices, a nod to afternoon readings in the studio.",
tags: ["hands", "mystic"],
size: "6in",
price: "$390",
availability: "waitlist",
gradient: "from-adobe via-terracotta to-brass",
detail: "Currently reserved for those on the waitlist — expect velvet textures in the modal.",
},
{
name: "Tilework Hearts",
theme: "Pattern",
story: "Repeating Talavera tiles hugging tiny hearts, meant for couples or BFF appointments.",
tags: ["pattern", "micro"],
size: "3in",
price: "$210",
availability: "bookable",
gradient: "from-parchment via-sage to-olive",
detail: "Available as mirrored pair or single strip.",
},
{
name: "Mochi's Crown",
theme: "Memorial",
story: "A crown of catnip, stars, and tassels dedicated to Mochi's tender presence.",
tags: ["memorial", "cat"],
size: "4in",
price: "$275",
availability: "archived",
gradient: "from-coffee via-terracotta to-iron",
detail: "Only available for family + close friends. Lives in the memorial set.",
},
];

66
src/data/music.ts Normal file
View File

@ -0,0 +1,66 @@
export type Playlist = {
title: string;
caption: string;
mood: string;
length: string;
color: string;
tracks: string[];
embedUrl: string;
};
export const playlists: Playlist[] = [
{
title: "Spanish Garden",
caption: "Soft nylon strings + birds to soundtrack flash sketching.",
mood: "Morning sun",
length: "1 hr 12 min",
color: "from-olive via-sage to-parchment",
tracks: ["Cuco — keeping tabs", "Helado Negro — Outside The Outside", "Ambar Lucid — La Torre"],
embedUrl: "https://open.spotify.com/embed/playlist/0HIPsLb0tr6j0qaaV40SfR?utm_source=generator&theme=0",
},
{
title: "Courtyard Pulse",
caption: "Percussion-heavy beats for lining days.",
mood: "Focus",
length: "48 min",
color: "from-terracotta via-adobe to-brass",
tracks: ["Bomba Estéreo — Amar Así", "Yendry — Nena", "Buscabulla — Ta Que Ti"],
embedUrl: "https://open.spotify.com/embed/playlist/4JZVf8Rj6XWrQTqG7z0vPQ?utm_source=generator",
},
{
title: "Velvet Lantern Night",
caption: "Late studio clean up with soft R&B and vinyl crackle.",
mood: "After hours",
length: "2 hr 05 min",
color: "from-coffee via-iron to-terracotta",
tracks: ["Bad Bunny — Andrea", "SZA — Saturn", "Kali Uchis — After The Storm"],
embedUrl: "https://open.spotify.com/embed/playlist/4XtBvK0gy4E9FnW7E3UHkc?utm_source=generator",
},
{
title: "Mochi's Lullaby",
caption: "Gentle instrumentals dedicated to the cat memorial corner.",
mood: "Tender",
length: "36 min",
color: "from-parchment via-sage to-olive",
tracks: ["Daniela Andrade — Crazy", "Devendra Banhart — Mi Negrita", "Men I Trust — Show Me How"],
embedUrl: "https://open.spotify.com/embed/playlist/2doqSQNDfjcbFCrz3fjLzx?utm_source=generator",
},
];
export const audioNotes = [
{
title: "Voice Memo — New Flash Pitch",
detail: "30-second vocal riff describing the lantern novena storyline.",
duration: "0:30",
},
{
title: "Bass Loop — Courtyard",
detail: "Hand-played bass + snaps, uploaded directly for layering in reels.",
duration: "1:12",
},
{
title: "Whisper Letter",
detail: "Personal Casa Azul note with reverb and parchment rustle.",
duration: "0:58",
},
];

58
src/data/pantry.ts Normal file
View File

@ -0,0 +1,58 @@
export type Recipe = {
name: string;
occasion: string;
description: string;
ingredients: string[];
accent: string;
};
export const recipes: Recipe[] = [
{
name: "Saffron Chickpea Stew",
occasion: "Studio reset",
description: "Slow simmered tomatoes, chickpeas, and citrus served with charred bread.",
ingredients: ["saffron threads", "fire-roasted tomatoes", "citrus peel", "cavolo nero"],
accent: "from-terracotta via-adobe to-brass",
},
{
name: "Olive Oil Citrus Cake",
occasion: "Client celebration",
description: "Single-layer cake brushed with orange blossom syrup.",
ingredients: ["olive oil", "semolina", "orange zest", "yogurt"],
accent: "from-parchment via-brass to-terracotta",
},
{
name: "Tapas Prep Board",
occasion: "Drop-in days",
description: "Marinated olives, manchego, pickled grapes, and rosemary almonds.",
ingredients: ["castelvetrano", "manchego", "almonds", "rosemary"],
accent: "from-olive via-sage to-parchment",
},
];
export const mealPrep = [
{ day: "Mon", plan: "Pantry curry + roasted potatoes" },
{ day: "Wed", plan: "Cold soba, pickled cukes, sesame oil" },
{ day: "Fri", plan: "Leftover tapas + citrus spritz" },
];
export const groceryList = [
{ item: "Oranges", status: "fresh" },
{ item: "Capers", status: "restock" },
{ item: "Oat milk", status: "low" },
{ item: "Fresh herbs", status: "needed" },
];
export const pantryInventory = [
{ category: "Grains", items: ["Arborio", "Farro", "Blue corn masa"] },
{ category: "Proteins", items: ["Chickpeas", "Lentils", "Tinned fish"] },
{ category: "Spices", items: ["Smoked paprika", "Saffron", "Cumin seeds"] },
{ category: "Snacks", items: ["Marcona almonds", "Dried figs", "Chocolate"], },
];
export const spinnerMeals = [
"Caldo verde",
"Smoky cauliflower tacos",
"Golden hour picnic board",
"Herb focaccia + soup",
];

48
src/data/scrapbook.ts Normal file
View File

@ -0,0 +1,48 @@
export type AssetCategory = {
name: string;
description: string;
items: string[];
};
export const scrapbookAssets: AssetCategory[] = [
{
name: "Uploads",
description: "Drop personal photos, scans, or inspo pulled from the courtyard walk.",
items: ["Studio Polaroids", "Client Moodboards", "Travel Film"],
},
{
name: "Stickers",
description: "Roses, spiderwebs, brass filigree, and wax seals to layer over tiles.",
items: ["Brass Lantern", "Desert Rose", "Handwritten Ribbon"],
},
{
name: "Textures",
description: "Paper scraps, gauze, and stucco flecks for tactile chaos.",
items: ["Parchment Wash", "Aged Stucco", "Torn Journal Edge"],
},
{
name: "Color Swatches",
description: "Saved palette chips from terracotta, adobe, and sage references.",
items: ["Terracotta #C46A4A", "Olive #7D8E6A", "Parchment #FDF8F1"],
},
{
name: "Notes",
description: "Serif or Caveat text blocks for quotes, recipes, or client names.",
items: ["Caveat Quick Note", "Crimson Story Box", "Typewriter Label"],
},
];
export const scrapbookTools = [
{ name: "Rotate", detail: "Use the brass dial to spin items ±45° for impromptu dynamism." },
{ name: "Scale", detail: "Pinch or drag edges scaling keeps texture clarity intact." },
{ name: "Flip", detail: "Mirror roses or tapes to keep compositions balanced." },
{ name: "Duplicate", detail: "Stamp repeating borders before recoloring swatches." },
{ name: "Layer", detail: "Send lanterns behind Polaroids or lock frames up front." },
{ name: "Autosave", detail: "Local JSON snapshot stored on each move, so experiments are safe." },
];
export const snapshotIdeas = [
"Pair arches with playlist covers for a music-inspo manifest.",
"Drop recipe cards beside grocery inventory before Sunday markets.",
"Pin cat Polaroids under Spanish tiles, then export for prints.",
];

View File

@ -15,6 +15,12 @@
"binding": "ASSETS",
"directory": ".open-next/assets"
},
"routes": [
{
"pattern": "grimmtattoo.ink/*",
"zone_name": "grimmtattoo.ink"
}
],
"observability": {
"enabled": true
}