Created stories:

- docs/stories/pub-1-shadcn-ui-consistency.md
- docs/stories/pub-2-parallax-split-hero.md
- docs/stories/pub-3-search-with-filters.md
- docs/stories/pub-4-quick-search-cmdk.md
- docs/stories/pub-5-aftercare-enhancements.md
- docs/stories/pub-6-galleries-lightbox.md

Each story:

- Mirrors PRD acceptance criteria verbatim
- Includes actionable Tasks/Subtasks aligned to repo components (e.g., components/hero-section.tsx, components/artist-portfolio.tsx, data/artists.ts)
- Provides Dev Notes referencing docs/ui-architecture.md and docs/PRD.md
- Includes Testing guidance (Vitest/RTL and a11y expectations)
- Reserves Dev Agent Record and QA Results sections for subsequent phases
This commit is contained in:
Nicholai 2025-09-19 21:45:59 -06:00
parent b20db98051
commit 2e4d44a881
59 changed files with 1544 additions and 1047 deletions

View File

@ -155,7 +155,7 @@ var DOQueueHandler = class extends DurableObject {
method: "HEAD",
headers: {
// This is defined during build
"x-prerender-revalidate": "88d6565b640e48aee50946d953a2e540",
"x-prerender-revalidate": "357089a5a8857bac42c7016153480f60",
"x-isr": "1"
},
// This one is kind of problematic, it will always show the wall time of the revalidation to `this.revalidationTimeout`
@ -179,7 +179,7 @@ var DOQueueHandler = class extends DurableObject {
"INSERT OR REPLACE INTO sync (id, lastSuccess, buildId) VALUES (?, unixepoch(), ?)",
// We cannot use the deduplication id because it's not unique per route - every time a route is revalidated, the deduplication id is different.
`${host}${url}`,
"Wg86ttPbchbaXr-QgMl_6"
"moUXVtzs2Bhk2eoEY-sub"
);
}
this.routeInFailedState.delete(msg.MessageDeduplicationId);
@ -231,7 +231,7 @@ var DOQueueHandler = class extends DurableObject {
}
this.routeInFailedState.set(msg.MessageDeduplicationId, updatedFailedState);
if (!this.disableSQLite) {
this.sql.exec("INSERT OR REPLACE INTO failed_state (id, data, buildId) VALUES (?, ?, ?)", msg.MessageDeduplicationId, JSON.stringify(updatedFailedState), "Wg86ttPbchbaXr-QgMl_6");
this.sql.exec("INSERT OR REPLACE INTO failed_state (id, data, buildId) VALUES (?, ?, ?)", msg.MessageDeduplicationId, JSON.stringify(updatedFailedState), "moUXVtzs2Bhk2eoEY-sub");
}
await this.addAlarm();
}
@ -255,8 +255,8 @@ var DOQueueHandler = class extends DurableObject {
return;
this.sql.exec("CREATE TABLE IF NOT EXISTS failed_state (id TEXT PRIMARY KEY, data TEXT, buildId TEXT)");
this.sql.exec("CREATE TABLE IF NOT EXISTS sync (id TEXT PRIMARY KEY, lastSuccess INTEGER, buildId TEXT)");
this.sql.exec("DELETE FROM failed_state WHERE buildId != ?", "Wg86ttPbchbaXr-QgMl_6");
this.sql.exec("DELETE FROM sync WHERE buildId != ?", "Wg86ttPbchbaXr-QgMl_6");
this.sql.exec("DELETE FROM failed_state WHERE buildId != ?", "moUXVtzs2Bhk2eoEY-sub");
this.sql.exec("DELETE FROM sync WHERE buildId != ?", "moUXVtzs2Bhk2eoEY-sub");
const failedStateCursor = this.sql.exec("SELECT * FROM failed_state");
for (const row of failedStateCursor) {
this.routeInFailedState.set(row.id, JSON.parse(row.data));

View File

@ -1 +1 @@
Wg86ttPbchbaXr-QgMl_6
moUXVtzs2Bhk2eoEY-sub

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"type":"app","html":"<!DOCTYPE html><html><head><meta charSet=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width\"/><title>500: Internal Server Error</title><meta name=\"next-head-count\" content=\"3\"/><noscript data-n-css=\"\"></noscript><script defer=\"\" nomodule=\"\" src=\"/_next/static/chunks/polyfills-42372ed130431b0a.js\"></script><script src=\"/_next/static/chunks/webpack-757604220b96f05e.js\" defer=\"\"></script><script src=\"/_next/static/chunks/framework-8e0e0f4a6b83a956.js\" defer=\"\"></script><script src=\"/_next/static/chunks/main-c7b74b84e134a397.js\" defer=\"\"></script><script src=\"/_next/static/chunks/pages/_app-3c9ca398d360b709.js\" defer=\"\"></script><script src=\"/_next/static/chunks/pages/_error-cf5ca766ac8f493f.js\" defer=\"\"></script><script src=\"/_next/static/Wg86ttPbchbaXr-QgMl_6/_buildManifest.js\" defer=\"\"></script><script src=\"/_next/static/Wg86ttPbchbaXr-QgMl_6/_ssgManifest.js\" defer=\"\"></script></head><body><div id=\"__next\"><div style=\"font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center\"><div style=\"line-height:48px\"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class=\"next-error-h1\" style=\"display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top\">500</h1><div style=\"display:inline-block\"><h2 style=\"font-size:14px;font-weight:400;line-height:28px\">Internal Server Error<!-- -->.</h2></div></div></div></div><script id=\"__NEXT_DATA__\" type=\"application/json\">{\"props\":{\"pageProps\":{\"statusCode\":500}},\"page\":\"/_error\",\"query\":{},\"buildId\":\"Wg86ttPbchbaXr-QgMl_6\",\"nextExport\":true,\"isFallback\":false,\"gip\":true,\"scriptLoader\":[]}</script></body></html>"}
{"type":"app","html":"<!DOCTYPE html><html><head><meta charSet=\"utf-8\"/><meta name=\"viewport\" content=\"width=device-width\"/><title>500: Internal Server Error</title><meta name=\"next-head-count\" content=\"3\"/><noscript data-n-css=\"\"></noscript><script defer=\"\" nomodule=\"\" src=\"/_next/static/chunks/polyfills-42372ed130431b0a.js\"></script><script src=\"/_next/static/chunks/webpack-757604220b96f05e.js\" defer=\"\"></script><script src=\"/_next/static/chunks/framework-8e0e0f4a6b83a956.js\" defer=\"\"></script><script src=\"/_next/static/chunks/main-c7b74b84e134a397.js\" defer=\"\"></script><script src=\"/_next/static/chunks/pages/_app-3c9ca398d360b709.js\" defer=\"\"></script><script src=\"/_next/static/chunks/pages/_error-cf5ca766ac8f493f.js\" defer=\"\"></script><script src=\"/_next/static/moUXVtzs2Bhk2eoEY-sub/_buildManifest.js\" defer=\"\"></script><script src=\"/_next/static/moUXVtzs2Bhk2eoEY-sub/_ssgManifest.js\" defer=\"\"></script></head><body><div id=\"__next\"><div style=\"font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center\"><div style=\"line-height:48px\"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class=\"next-error-h1\" style=\"display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top\">500</h1><div style=\"display:inline-block\"><h2 style=\"font-size:14px;font-weight:400;line-height:28px\">Internal Server Error<!-- -->.</h2></div></div></div></div><script id=\"__NEXT_DATA__\" type=\"application/json\">{\"props\":{\"pageProps\":{\"statusCode\":500}},\"page\":\"/_error\",\"query\":{},\"buildId\":\"moUXVtzs2Bhk2eoEY-sub\",\"nextExport\":true,\"isFallback\":false,\"gip\":true,\"scriptLoader\":[]}</script></body></html>"}

View File

@ -1,3 +1,3 @@
CREATE TABLE IF NOT EXISTS tags (tag TEXT NOT NULL, path TEXT NOT NULL, UNIQUE(tag, path) ON CONFLICT REPLACE);
CREATE TABLE IF NOT EXISTS revalidations (tag TEXT NOT NULL, revalidatedAt INTEGER NOT NULL, UNIQUE(tag) ON CONFLICT REPLACE);
INSERT INTO tags (tag, path) VALUES ("Wg86ttPbchbaXr-QgMl_6/_N_T_/layout", "Wg86ttPbchbaXr-QgMl_6/favicon.ico"), ("Wg86ttPbchbaXr-QgMl_6/_N_T_/favicon.ico/layout", "Wg86ttPbchbaXr-QgMl_6/favicon.ico"), ("Wg86ttPbchbaXr-QgMl_6/_N_T_/favicon.ico/route", "Wg86ttPbchbaXr-QgMl_6/favicon.ico"), ("Wg86ttPbchbaXr-QgMl_6/_N_T_/favicon.ico", "Wg86ttPbchbaXr-QgMl_6/favicon.ico");
INSERT INTO tags (tag, path) VALUES ("moUXVtzs2Bhk2eoEY-sub/_N_T_/layout", "moUXVtzs2Bhk2eoEY-sub/favicon.ico"), ("moUXVtzs2Bhk2eoEY-sub/_N_T_/favicon.ico/layout", "moUXVtzs2Bhk2eoEY-sub/favicon.ico"), ("moUXVtzs2Bhk2eoEY-sub/_N_T_/favicon.ico/route", "moUXVtzs2Bhk2eoEY-sub/favicon.ico"), ("moUXVtzs2Bhk2eoEY-sub/_N_T_/favicon.ico", "moUXVtzs2Bhk2eoEY-sub/favicon.ico");

View File

@ -49,7 +49,7 @@ function initRuntime() {
};
Object.assign(globalThis, {
Request: CustomRequest,
__BUILD_TIMESTAMP_MS__: 1758335832711,
__BUILD_TIMESTAMP_MS__: 1758339813791,
__NEXT_BASE_PATH__: "",
__ASSETS_RUN_WORKER_FIRST__: false,
__TRAILING_SLASH__: false,

View File

@ -1 +1 @@
[{"tag":{"S":"Wg86ttPbchbaXr-QgMl_6/_N_T_/layout"},"path":{"S":"Wg86ttPbchbaXr-QgMl_6/favicon.ico"},"revalidatedAt":{"N":"1"}},{"tag":{"S":"Wg86ttPbchbaXr-QgMl_6/_N_T_/favicon.ico/layout"},"path":{"S":"Wg86ttPbchbaXr-QgMl_6/favicon.ico"},"revalidatedAt":{"N":"1"}},{"tag":{"S":"Wg86ttPbchbaXr-QgMl_6/_N_T_/favicon.ico/route"},"path":{"S":"Wg86ttPbchbaXr-QgMl_6/favicon.ico"},"revalidatedAt":{"N":"1"}},{"tag":{"S":"Wg86ttPbchbaXr-QgMl_6/_N_T_/favicon.ico"},"path":{"S":"Wg86ttPbchbaXr-QgMl_6/favicon.ico"},"revalidatedAt":{"N":"1"}}]
[{"tag":{"S":"moUXVtzs2Bhk2eoEY-sub/_N_T_/layout"},"path":{"S":"moUXVtzs2Bhk2eoEY-sub/favicon.ico"},"revalidatedAt":{"N":"1"}},{"tag":{"S":"moUXVtzs2Bhk2eoEY-sub/_N_T_/favicon.ico/layout"},"path":{"S":"moUXVtzs2Bhk2eoEY-sub/favicon.ico"},"revalidatedAt":{"N":"1"}},{"tag":{"S":"moUXVtzs2Bhk2eoEY-sub/_N_T_/favicon.ico/route"},"path":{"S":"moUXVtzs2Bhk2eoEY-sub/favicon.ico"},"revalidatedAt":{"N":"1"}},{"tag":{"S":"moUXVtzs2Bhk2eoEY-sub/_N_T_/favicon.ico"},"path":{"S":"moUXVtzs2Bhk2eoEY-sub/favicon.ico"},"revalidatedAt":{"N":"1"}}]

View File

@ -5255,13 +5255,13 @@ var NEXT_DIR = path.join(__dirname, ".next");
var OPEN_NEXT_DIR = path.join(__dirname, ".open-next");
debug({ NEXT_DIR, OPEN_NEXT_DIR });
var NextConfig = { "env": {}, "webpack": null, "eslint": { "ignoreDuringBuilds": true }, "typescript": { "ignoreBuildErrors": true, "tsconfigPath": "tsconfig.json" }, "distDir": ".next", "cleanDistDir": true, "assetPrefix": "", "cacheMaxMemorySize": 52428800, "configOrigin": "next.config.mjs", "useFileSystemPublicRoutes": true, "generateEtags": true, "pageExtensions": ["tsx", "ts", "jsx", "js"], "poweredByHeader": true, "compress": true, "analyticsId": "", "images": { "deviceSizes": [640, 750, 828, 1080, 1200, 1920, 2048, 3840], "imageSizes": [16, 32, 48, 64, 96, 128, 256, 384], "path": "/_next/image", "loader": "default", "loaderFile": "", "domains": [], "disableStaticImages": false, "minimumCacheTTL": 60, "formats": ["image/webp"], "dangerouslyAllowSVG": false, "contentSecurityPolicy": "script-src 'none'; frame-src 'none'; sandbox;", "contentDispositionType": "inline", "remotePatterns": [], "unoptimized": true }, "devIndicators": { "buildActivity": true, "buildActivityPosition": "bottom-right" }, "onDemandEntries": { "maxInactiveAge": 6e4, "pagesBufferLength": 5 }, "amp": { "canonicalBase": "" }, "basePath": "", "sassOptions": {}, "trailingSlash": false, "i18n": null, "productionBrowserSourceMaps": false, "optimizeFonts": true, "excludeDefaultMomentLocales": true, "serverRuntimeConfig": {}, "publicRuntimeConfig": {}, "reactProductionProfiling": false, "reactStrictMode": null, "httpAgentOptions": { "keepAlive": true }, "outputFileTracing": true, "staticPageGenerationTimeout": 60, "swcMinify": true, "output": "standalone", "modularizeImports": { "@mui/icons-material": { "transform": "@mui/icons-material/{{member}}" }, "lodash": { "transform": "lodash/{{member}}" } }, "experimental": { "multiZoneDraftMode": false, "prerenderEarlyExit": false, "serverMinification": true, "serverSourceMaps": false, "linkNoTouchStart": false, "caseSensitiveRoutes": false, "clientRouterFilter": true, "clientRouterFilterRedirects": false, "fetchCacheKeyPrefix": "", "middlewarePrefetch": "flexible", "optimisticClientCache": true, "manualClientBasePath": false, "cpus": 11, "memoryBasedWorkersCount": false, "isrFlushToDisk": true, "workerThreads": false, "optimizeCss": false, "nextScriptWorkers": false, "scrollRestoration": false, "externalDir": false, "disableOptimizedLoading": false, "gzipSize": true, "craCompat": false, "esmExternals": true, "fullySpecified": false, "outputFileTracingRoot": "/home/Nicholai/Documents/Dev/united_v03/united-tattoo/united-tattoo", "swcTraceProfiling": false, "forceSwcTransforms": false, "largePageDataBytes": 128e3, "adjustFontFallbacks": false, "adjustFontFallbacksWithSizeAdjust": false, "typedRoutes": false, "instrumentationHook": false, "bundlePagesExternals": false, "parallelServerCompiles": false, "parallelServerBuildTraces": false, "ppr": false, "missingSuspenseWithCSRBailout": true, "optimizeServerReact": true, "useEarlyImport": false, "staleTimes": { "dynamic": 30, "static": 300 }, "optimizePackageImports": ["lucide-react", "date-fns", "lodash-es", "ramda", "antd", "react-bootstrap", "ahooks", "@ant-design/icons", "@headlessui/react", "@headlessui-float/react", "@heroicons/react/20/solid", "@heroicons/react/24/solid", "@heroicons/react/24/outline", "@visx/visx", "@tremor/react", "rxjs", "@mui/material", "@mui/icons-material", "recharts", "react-use", "@material-ui/core", "@material-ui/icons", "@tabler/icons-react", "mui-core", "react-icons/ai", "react-icons/bi", "react-icons/bs", "react-icons/cg", "react-icons/ci", "react-icons/di", "react-icons/fa", "react-icons/fa6", "react-icons/fc", "react-icons/fi", "react-icons/gi", "react-icons/go", "react-icons/gr", "react-icons/hi", "react-icons/hi2", "react-icons/im", "react-icons/io", "react-icons/io5", "react-icons/lia", "react-icons/lib", "react-icons/lu", "react-icons/md", "react-icons/pi", "react-icons/ri", "react-icons/rx", "react-icons/si", "react-icons/sl", "react-icons/tb", "react-icons/tfi", "react-icons/ti", "react-icons/vsc", "react-icons/wi"], "trustHostHeader": false, "isExperimentalCompile": false }, "configFileName": "next.config.mjs" };
var BuildId = "Wg86ttPbchbaXr-QgMl_6";
var BuildId = "moUXVtzs2Bhk2eoEY-sub";
var RoutesManifest = { "basePath": "", "rewrites": { "beforeFiles": [], "afterFiles": [], "fallback": [] }, "redirects": [{ "source": "/:path+/", "destination": "/:path+", "internal": true, "statusCode": 308, "regex": "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$" }], "routes": { "static": [{ "page": "/", "regex": "^/(?:/)?$", "routeKeys": {}, "namedRegex": "^/(?:/)?$" }, { "page": "/_not-found", "regex": "^/_not\\-found(?:/)?$", "routeKeys": {}, "namedRegex": "^/_not\\-found(?:/)?$" }, { "page": "/admin", "regex": "^/admin(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin(?:/)?$" }, { "page": "/admin/analytics", "regex": "^/admin/analytics(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/analytics(?:/)?$" }, { "page": "/admin/artists", "regex": "^/admin/artists(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/artists(?:/)?$" }, { "page": "/admin/artists/new", "regex": "^/admin/artists/new(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/artists/new(?:/)?$" }, { "page": "/admin/calendar", "regex": "^/admin/calendar(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/calendar(?:/)?$" }, { "page": "/admin/portfolio", "regex": "^/admin/portfolio(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/portfolio(?:/)?$" }, { "page": "/admin/settings", "regex": "^/admin/settings(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/settings(?:/)?$" }, { "page": "/admin/uploads", "regex": "^/admin/uploads(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/uploads(?:/)?$" }, { "page": "/aftercare", "regex": "^/aftercare(?:/)?$", "routeKeys": {}, "namedRegex": "^/aftercare(?:/)?$" }, { "page": "/artists", "regex": "^/artists(?:/)?$", "routeKeys": {}, "namedRegex": "^/artists(?:/)?$" }, { "page": "/auth/error", "regex": "^/auth/error(?:/)?$", "routeKeys": {}, "namedRegex": "^/auth/error(?:/)?$" }, { "page": "/auth/signin", "regex": "^/auth/signin(?:/)?$", "routeKeys": {}, "namedRegex": "^/auth/signin(?:/)?$" }, { "page": "/book", "regex": "^/book(?:/)?$", "routeKeys": {}, "namedRegex": "^/book(?:/)?$" }, { "page": "/contact", "regex": "^/contact(?:/)?$", "routeKeys": {}, "namedRegex": "^/contact(?:/)?$" }, { "page": "/deposit", "regex": "^/deposit(?:/)?$", "routeKeys": {}, "namedRegex": "^/deposit(?:/)?$" }, { "page": "/favicon.ico", "regex": "^/favicon\\.ico(?:/)?$", "routeKeys": {}, "namedRegex": "^/favicon\\.ico(?:/)?$" }, { "page": "/gift-cards", "regex": "^/gift\\-cards(?:/)?$", "routeKeys": {}, "namedRegex": "^/gift\\-cards(?:/)?$" }, { "page": "/privacy", "regex": "^/privacy(?:/)?$", "routeKeys": {}, "namedRegex": "^/privacy(?:/)?$" }, { "page": "/specials", "regex": "^/specials(?:/)?$", "routeKeys": {}, "namedRegex": "^/specials(?:/)?$" }, { "page": "/terms", "regex": "^/terms(?:/)?$", "routeKeys": {}, "namedRegex": "^/terms(?:/)?$" }], "dynamic": [{ "page": "/admin/artists/[id]", "regex": "^/admin/artists/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/admin/artists/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/api/artists/[id]", "regex": "^/api/artists/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/api/artists/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/api/auth/[...nextauth]", "regex": "^/api/auth/(.+?)(?:/)?$", "routeKeys": { "nxtPnextauth": "nxtPnextauth" }, "namedRegex": "^/api/auth/(?<nxtPnextauth>.+?)(?:/)?$" }, { "page": "/api/portfolio/[id]", "regex": "^/api/portfolio/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/api/portfolio/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/artists/[id]", "regex": "^/artists/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/artists/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/artists/[id]/book", "regex": "^/artists/([^/]+?)/book(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/artists/(?<nxtPid>[^/]+?)/book(?:/)?$" }], "data": { "static": [], "dynamic": [] } }, "locales": [] };
var ConfigHeaders = [];
var PrerenderManifest = { "version": 4, "routes": { "/favicon.ico": { "initialHeaders": { "cache-control": "public, max-age=0, must-revalidate", "content-type": "image/x-icon", "x-next-cache-tags": "_N_T_/layout,_N_T_/favicon.ico/layout,_N_T_/favicon.ico/route,_N_T_/favicon.ico" }, "experimentalBypassFor": [{ "type": "header", "key": "Next-Action" }, { "type": "header", "key": "content-type", "value": "multipart/form-data;.*" }], "initialRevalidateSeconds": false, "srcRoute": "/favicon.ico", "dataRoute": null } }, "dynamicRoutes": {}, "notFoundRoutes": [], "preview": { "previewModeId": "88d6565b640e48aee50946d953a2e540", "previewModeSigningKey": "6942ce5017ba913dc3d9f31aa8c1b62cef0723664c92edea56b6aebbbeebc663", "previewModeEncryptionKey": "557cec8a7efe472f27a80fe51c78a386ecfc7bbaf73c297a8f9ad8988c889827" } };
var MiddlewareManifest = { "version": 3, "middleware": { "/": { "files": ["server/edge-runtime-webpack.js", "server/middleware.js"], "name": "middleware", "page": "/", "matchers": [{ "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!_next\\/static|_next\\/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*))(.json)?[\\/#\\?]?$", "originalSource": "/((?!_next/static|_next/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*)" }], "wasm": [], "assets": [], "env": { "__NEXT_BUILD_ID": "Wg86ttPbchbaXr-QgMl_6", "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "17081RAAyATuiotK5R7hh5isqj2lH5h4DOEGRt6U024=", "__NEXT_PREVIEW_MODE_ID": "88d6565b640e48aee50946d953a2e540", "__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "557cec8a7efe472f27a80fe51c78a386ecfc7bbaf73c297a8f9ad8988c889827", "__NEXT_PREVIEW_MODE_SIGNING_KEY": "6942ce5017ba913dc3d9f31aa8c1b62cef0723664c92edea56b6aebbbeebc663" } } }, "functions": {}, "sortedMiddleware": ["/"] };
var AppPathRoutesManifest = { "/_not-found/page": "/_not-found", "/api/artists/[id]/route": "/api/artists/[id]", "/aftercare/page": "/aftercare", "/api/admin/migrate/route": "/api/admin/migrate", "/api/auth/[...nextauth]/route": "/api/auth/[...nextauth]", "/artists/[id]/book/page": "/artists/[id]/book", "/artists/page": "/artists", "/artists/[id]/page": "/artists/[id]", "/auth/error/page": "/auth/error", "/auth/signin/page": "/auth/signin", "/book/page": "/book", "/contact/page": "/contact", "/gift-cards/page": "/gift-cards", "/deposit/page": "/deposit", "/page": "/", "/specials/page": "/specials", "/privacy/page": "/privacy", "/favicon.ico/route": "/favicon.ico", "/terms/page": "/terms", "/api/admin/stats/route": "/api/admin/stats", "/api/files/bulk-delete/route": "/api/files/bulk-delete", "/api/files/folder/route": "/api/files/folder", "/api/artists/route": "/api/artists", "/api/portfolio/bulk-delete/route": "/api/portfolio/bulk-delete", "/api/portfolio/stats/route": "/api/portfolio/stats", "/api/appointments/route": "/api/appointments", "/api/files/stats/route": "/api/files/stats", "/api/files/route": "/api/files", "/api/portfolio/route": "/api/portfolio", "/api/portfolio/[id]/route": "/api/portfolio/[id]", "/api/users/route": "/api/users", "/api/upload/route": "/api/upload", "/api/settings/route": "/api/settings", "/admin/artists/page": "/admin/artists", "/admin/artists/new/page": "/admin/artists/new", "/admin/artists/[id]/page": "/admin/artists/[id]", "/admin/page": "/admin", "/admin/calendar/page": "/admin/calendar", "/admin/portfolio/page": "/admin/portfolio", "/admin/settings/page": "/admin/settings", "/admin/analytics/page": "/admin/analytics", "/admin/uploads/page": "/admin/uploads" };
var FunctionsConfigManifest = { "version": 1, "functions": { "/api/artists": {}, "/api/admin/stats": {}, "/api/files/folder": {}, "/api/files/bulk-delete": {}, "/api/files/stats": {}, "/api/files": {}, "/api/appointments": {}, "/api/portfolio/bulk-delete": {}, "/api/portfolio/stats": {}, "/api/portfolio/[id]": {}, "/api/portfolio": {}, "/api/settings": {}, "/api/users": {}, "/api/upload": {}, "/admin/settings": {}, "/admin/uploads": {}, "/admin/portfolio": {}, "/admin/analytics": {} } };
var PrerenderManifest = { "version": 4, "routes": { "/favicon.ico": { "initialHeaders": { "cache-control": "public, max-age=0, must-revalidate", "content-type": "image/x-icon", "x-next-cache-tags": "_N_T_/layout,_N_T_/favicon.ico/layout,_N_T_/favicon.ico/route,_N_T_/favicon.ico" }, "experimentalBypassFor": [{ "type": "header", "key": "Next-Action" }, { "type": "header", "key": "content-type", "value": "multipart/form-data;.*" }], "initialRevalidateSeconds": false, "srcRoute": "/favicon.ico", "dataRoute": null } }, "dynamicRoutes": {}, "notFoundRoutes": [], "preview": { "previewModeId": "357089a5a8857bac42c7016153480f60", "previewModeSigningKey": "7e9864116e15094f48442bf7a8975abc6d20440dc377454c5a4ba43bcf8dfed8", "previewModeEncryptionKey": "068ad86c40815751aaee20559bb2914de03a50bf0d7b9a358b02e3359699c8c9" } };
var MiddlewareManifest = { "version": 3, "middleware": { "/": { "files": ["server/edge-runtime-webpack.js", "server/middleware.js"], "name": "middleware", "page": "/", "matchers": [{ "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!_next\\/static|_next\\/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*))(.json)?[\\/#\\?]?$", "originalSource": "/((?!_next/static|_next/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*)" }], "wasm": [], "assets": [], "env": { "__NEXT_BUILD_ID": "moUXVtzs2Bhk2eoEY-sub", "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "a5w8Al/LceGAEBAlCmLaVGy1uGjWkyVXJJF7qa6zvfU=", "__NEXT_PREVIEW_MODE_ID": "357089a5a8857bac42c7016153480f60", "__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "068ad86c40815751aaee20559bb2914de03a50bf0d7b9a358b02e3359699c8c9", "__NEXT_PREVIEW_MODE_SIGNING_KEY": "7e9864116e15094f48442bf7a8975abc6d20440dc377454c5a4ba43bcf8dfed8" } } }, "functions": {}, "sortedMiddleware": ["/"] };
var AppPathRoutesManifest = { "/_not-found/page": "/_not-found", "/aftercare/page": "/aftercare", "/api/admin/migrate/route": "/api/admin/migrate", "/api/artists/[id]/route": "/api/artists/[id]", "/api/auth/[...nextauth]/route": "/api/auth/[...nextauth]", "/artists/[id]/book/page": "/artists/[id]/book", "/artists/[id]/page": "/artists/[id]", "/artists/page": "/artists", "/book/page": "/book", "/contact/page": "/contact", "/deposit/page": "/deposit", "/favicon.ico/route": "/favicon.ico", "/auth/error/page": "/auth/error", "/auth/signin/page": "/auth/signin", "/gift-cards/page": "/gift-cards", "/page": "/", "/specials/page": "/specials", "/privacy/page": "/privacy", "/terms/page": "/terms", "/api/admin/stats/route": "/api/admin/stats", "/api/artists/route": "/api/artists", "/api/files/bulk-delete/route": "/api/files/bulk-delete", "/api/files/folder/route": "/api/files/folder", "/api/files/route": "/api/files", "/api/files/stats/route": "/api/files/stats", "/api/appointments/route": "/api/appointments", "/api/portfolio/[id]/route": "/api/portfolio/[id]", "/api/portfolio/bulk-delete/route": "/api/portfolio/bulk-delete", "/api/portfolio/stats/route": "/api/portfolio/stats", "/api/portfolio/route": "/api/portfolio", "/api/upload/route": "/api/upload", "/api/settings/route": "/api/settings", "/api/users/route": "/api/users", "/admin/artists/[id]/page": "/admin/artists/[id]", "/admin/artists/new/page": "/admin/artists/new", "/admin/artists/page": "/admin/artists", "/admin/calendar/page": "/admin/calendar", "/admin/page": "/admin", "/admin/portfolio/page": "/admin/portfolio", "/admin/settings/page": "/admin/settings", "/admin/uploads/page": "/admin/uploads", "/admin/analytics/page": "/admin/analytics" };
var FunctionsConfigManifest = { "version": 1, "functions": { "/api/admin/stats": {}, "/api/artists": {}, "/api/files/folder": {}, "/api/files/bulk-delete": {}, "/api/files": {}, "/api/files/stats": {}, "/api/appointments": {}, "/api/portfolio/bulk-delete": {}, "/api/portfolio/[id]": {}, "/api/portfolio/stats": {}, "/api/upload": {}, "/api/users": {}, "/admin/portfolio": {}, "/admin/settings": {}, "/admin/uploads": {}, "/admin/analytics": {}, "/api/settings": {}, "/api/portfolio": {} } };
var PagesManifest = { "/_app": "pages/_app.js", "/_error": "pages/_error.js", "/_document": "pages/_document.js" };
process.env.NEXT_BUILD_ID = BuildId;

View File

@ -1 +1 @@
Wg86ttPbchbaXr-QgMl_6
moUXVtzs2Bhk2eoEY-sub

View File

@ -59,16 +59,6 @@
"static/chunks/3621-816f023d8637f8cf.js",
"static/chunks/app/artists/[id]/book/page-d0b8c735780f889a.js"
],
"/artists/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/9480-1ce7c39d703ac59d.js",
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/artists/page-c4f07a95f9bb2ef6.js"
],
"/artists/[id]/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
@ -79,23 +69,15 @@
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/artists/[id]/page-35ca6d272039db3d.js"
],
"/auth/error/page": [
"/artists/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/9480-1ce7c39d703ac59d.js",
"static/chunks/app/auth/error/page-d868067799cd7555.js"
],
"/auth/signin/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/605-45978c1393111857.js",
"static/chunks/app/auth/signin/page-35bf86482341b311.js"
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/artists/page-c4f07a95f9bb2ef6.js"
],
"/book/page": [
"static/chunks/webpack-757604220b96f05e.js",
@ -125,16 +107,6 @@
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/contact/page-746f1b38e3408667.js"
],
"/gift-cards/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/9480-1ce7c39d703ac59d.js",
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/gift-cards/page-14a7e5647c17d01a.js"
],
"/deposit/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
@ -146,6 +118,34 @@
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/deposit/page-847f632ea4b9f0ed.js"
],
"/auth/error/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/9480-1ce7c39d703ac59d.js",
"static/chunks/app/auth/error/page-d868067799cd7555.js"
],
"/auth/signin/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/605-45978c1393111857.js",
"static/chunks/app/auth/signin/page-35bf86482341b311.js"
],
"/gift-cards/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/9480-1ce7c39d703ac59d.js",
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/gift-cards/page-14a7e5647c17d01a.js"
],
"/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
@ -187,16 +187,15 @@
"static/chunks/5360-bc2074e47478f077.js",
"static/chunks/app/terms/page-aaf525cbc86c3013.js"
],
"/admin/artists/page": [
"/admin/artists/[id]/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/5922-83622b3dd74c301a.js",
"static/chunks/1289-cf6c0ce38c294aae.js",
"static/chunks/3897-76242b36080d12ac.js",
"static/chunks/app/admin/artists/page-01bca19ebf880e2a.js"
"static/chunks/7053-c3715a35a089c757.js",
"static/chunks/9504-c55e72c2df29d40b.js",
"static/chunks/app/admin/artists/[id]/page-ea9aa89b8cf9aaa2.js"
],
"/admin/layout": [
"static/chunks/webpack-757604220b96f05e.js",
@ -218,15 +217,36 @@
"static/chunks/9504-c55e72c2df29d40b.js",
"static/chunks/app/admin/artists/new/page-fc95720483d0cd2a.js"
],
"/admin/artists/[id]/page": [
"/admin/artists/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/5922-83622b3dd74c301a.js",
"static/chunks/1289-cf6c0ce38c294aae.js",
"static/chunks/3897-76242b36080d12ac.js",
"static/chunks/app/admin/artists/page-01bca19ebf880e2a.js"
],
"/admin/calendar/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/css/b3adf42d35f4dca6.css",
"static/chunks/e80c4f76-5fdfad51ef5e5399.js",
"static/chunks/13b76428-e1bf383848c17260.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/5922-83622b3dd74c301a.js",
"static/chunks/1289-cf6c0ce38c294aae.js",
"static/chunks/4975-3485f00cf2b3a8d4.js",
"static/chunks/7053-c3715a35a089c757.js",
"static/chunks/9504-c55e72c2df29d40b.js",
"static/chunks/app/admin/artists/[id]/page-ea9aa89b8cf9aaa2.js"
"static/chunks/9763-d1abb329345aa3ca.js",
"static/chunks/9027-db9c48cede8f0bf8.js",
"static/chunks/8115-56c26c6e04063d66.js",
"static/chunks/1432-ff04625ef7f88667.js",
"static/chunks/4196-d44cfe779e086520.js",
"static/chunks/app/admin/calendar/page-ca552927ad620e4f.js"
],
"/admin/page": [
"static/chunks/webpack-757604220b96f05e.js",
@ -236,30 +256,10 @@
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/9480-1ce7c39d703ac59d.js",
"static/chunks/9763-d1abb329345aa3ca.js",
"static/chunks/8115-e1e10ada2b495c2f.js",
"static/chunks/1061-d513b18480fcc8d3.js",
"static/chunks/8115-56c26c6e04063d66.js",
"static/chunks/1061-586ee83cce24e829.js",
"static/chunks/app/admin/page-905a3f6976e2b146.js"
],
"/admin/calendar/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/css/b3adf42d35f4dca6.css",
"static/chunks/e80c4f76-08242431e2943761.js",
"static/chunks/13b76428-e1bf383848c17260.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/5922-83622b3dd74c301a.js",
"static/chunks/1289-cf6c0ce38c294aae.js",
"static/chunks/4975-3485f00cf2b3a8d4.js",
"static/chunks/7053-c3715a35a089c757.js",
"static/chunks/9763-d1abb329345aa3ca.js",
"static/chunks/9027-db9c48cede8f0bf8.js",
"static/chunks/8115-e1e10ada2b495c2f.js",
"static/chunks/1432-ff04625ef7f88667.js",
"static/chunks/4196-ea64f36ef793cce4.js",
"static/chunks/app/admin/calendar/page-ca552927ad620e4f.js"
],
"/admin/portfolio/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
@ -288,15 +288,6 @@
"static/chunks/6298-bdd24abc342a9083.js",
"static/chunks/app/admin/settings/page-471ed2952d0bbc2b.js"
],
"/admin/analytics/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/200-bbf23af727164989.js",
"static/chunks/app/admin/analytics/page-bca467eb895c1a4d.js"
],
"/admin/uploads/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
@ -308,6 +299,15 @@
"static/chunks/971-51c9bb074b0cc686.js",
"static/chunks/6298-bdd24abc342a9083.js",
"static/chunks/app/admin/uploads/page-670b1b6fdbaa9929.js"
],
"/admin/analytics/page": [
"static/chunks/webpack-757604220b96f05e.js",
"static/chunks/fd9d1056-5b6690cdd32bcd09.js",
"static/chunks/2117-e88b003482e3bf2d.js",
"static/chunks/main-app-ac1aded1f8d8af62.js",
"static/chunks/6137-eaf7b6db0f76248f.js",
"static/chunks/200-bbf23af727164989.js",
"static/chunks/app/admin/analytics/page-bca467eb895c1a4d.js"
]
}
}

View File

@ -1 +1 @@
{"/_not-found/page":"/_not-found","/api/artists/[id]/route":"/api/artists/[id]","/aftercare/page":"/aftercare","/api/admin/migrate/route":"/api/admin/migrate","/api/auth/[...nextauth]/route":"/api/auth/[...nextauth]","/artists/[id]/book/page":"/artists/[id]/book","/artists/page":"/artists","/artists/[id]/page":"/artists/[id]","/auth/error/page":"/auth/error","/auth/signin/page":"/auth/signin","/book/page":"/book","/contact/page":"/contact","/gift-cards/page":"/gift-cards","/deposit/page":"/deposit","/page":"/","/specials/page":"/specials","/privacy/page":"/privacy","/favicon.ico/route":"/favicon.ico","/terms/page":"/terms","/api/admin/stats/route":"/api/admin/stats","/api/files/bulk-delete/route":"/api/files/bulk-delete","/api/files/folder/route":"/api/files/folder","/api/artists/route":"/api/artists","/api/portfolio/bulk-delete/route":"/api/portfolio/bulk-delete","/api/portfolio/stats/route":"/api/portfolio/stats","/api/appointments/route":"/api/appointments","/api/files/stats/route":"/api/files/stats","/api/files/route":"/api/files","/api/portfolio/route":"/api/portfolio","/api/portfolio/[id]/route":"/api/portfolio/[id]","/api/users/route":"/api/users","/api/upload/route":"/api/upload","/api/settings/route":"/api/settings","/admin/artists/page":"/admin/artists","/admin/artists/new/page":"/admin/artists/new","/admin/artists/[id]/page":"/admin/artists/[id]","/admin/page":"/admin","/admin/calendar/page":"/admin/calendar","/admin/portfolio/page":"/admin/portfolio","/admin/settings/page":"/admin/settings","/admin/analytics/page":"/admin/analytics","/admin/uploads/page":"/admin/uploads"}
{"/_not-found/page":"/_not-found","/aftercare/page":"/aftercare","/api/admin/migrate/route":"/api/admin/migrate","/api/artists/[id]/route":"/api/artists/[id]","/api/auth/[...nextauth]/route":"/api/auth/[...nextauth]","/artists/[id]/book/page":"/artists/[id]/book","/artists/[id]/page":"/artists/[id]","/artists/page":"/artists","/book/page":"/book","/contact/page":"/contact","/deposit/page":"/deposit","/favicon.ico/route":"/favicon.ico","/auth/error/page":"/auth/error","/auth/signin/page":"/auth/signin","/gift-cards/page":"/gift-cards","/page":"/","/specials/page":"/specials","/privacy/page":"/privacy","/terms/page":"/terms","/api/admin/stats/route":"/api/admin/stats","/api/artists/route":"/api/artists","/api/files/bulk-delete/route":"/api/files/bulk-delete","/api/files/folder/route":"/api/files/folder","/api/files/route":"/api/files","/api/files/stats/route":"/api/files/stats","/api/appointments/route":"/api/appointments","/api/portfolio/[id]/route":"/api/portfolio/[id]","/api/portfolio/bulk-delete/route":"/api/portfolio/bulk-delete","/api/portfolio/stats/route":"/api/portfolio/stats","/api/portfolio/route":"/api/portfolio","/api/upload/route":"/api/upload","/api/settings/route":"/api/settings","/api/users/route":"/api/users","/admin/artists/[id]/page":"/admin/artists/[id]","/admin/artists/new/page":"/admin/artists/new","/admin/artists/page":"/admin/artists","/admin/calendar/page":"/admin/calendar","/admin/page":"/admin","/admin/portfolio/page":"/admin/portfolio","/admin/settings/page":"/admin/settings","/admin/uploads/page":"/admin/uploads","/admin/analytics/page":"/admin/analytics"}

View File

@ -5,8 +5,8 @@
"devFiles": [],
"ampDevFiles": [],
"lowPriorityFiles": [
"static/Wg86ttPbchbaXr-QgMl_6/_buildManifest.js",
"static/Wg86ttPbchbaXr-QgMl_6/_ssgManifest.js"
"static/moUXVtzs2Bhk2eoEY-sub/_buildManifest.js",
"static/moUXVtzs2Bhk2eoEY-sub/_ssgManifest.js"
],
"rootMainFiles": [
"static/chunks/webpack-757604220b96f05e.js",

View File

@ -1 +1 @@
{"version":4,"routes":{"/favicon.ico":{"initialHeaders":{"cache-control":"public, max-age=0, must-revalidate","content-type":"image/x-icon","x-next-cache-tags":"_N_T_/layout,_N_T_/favicon.ico/layout,_N_T_/favicon.ico/route,_N_T_/favicon.ico"},"experimentalBypassFor":[{"type":"header","key":"Next-Action"},{"type":"header","key":"content-type","value":"multipart/form-data;.*"}],"initialRevalidateSeconds":false,"srcRoute":"/favicon.ico","dataRoute":null}},"dynamicRoutes":{},"notFoundRoutes":[],"preview":{"previewModeId":"88d6565b640e48aee50946d953a2e540","previewModeSigningKey":"6942ce5017ba913dc3d9f31aa8c1b62cef0723664c92edea56b6aebbbeebc663","previewModeEncryptionKey":"557cec8a7efe472f27a80fe51c78a386ecfc7bbaf73c297a8f9ad8988c889827"}}
{"version":4,"routes":{"/favicon.ico":{"initialHeaders":{"cache-control":"public, max-age=0, must-revalidate","content-type":"image/x-icon","x-next-cache-tags":"_N_T_/layout,_N_T_/favicon.ico/layout,_N_T_/favicon.ico/route,_N_T_/favicon.ico"},"experimentalBypassFor":[{"type":"header","key":"Next-Action"},{"type":"header","key":"content-type","value":"multipart/form-data;.*"}],"initialRevalidateSeconds":false,"srcRoute":"/favicon.ico","dataRoute":null}},"dynamicRoutes":{},"notFoundRoutes":[],"preview":{"previewModeId":"357089a5a8857bac42c7016153480f60","previewModeSigningKey":"7e9864116e15094f48442bf7a8975abc6d20440dc377454c5a4ba43bcf8dfed8","previewModeEncryptionKey":"068ad86c40815751aaee20559bb2914de03a50bf0d7b9a358b02e3359699c8c9"}}

View File

@ -1,44 +1,44 @@
{
"/_not-found/page": "app/_not-found/page.js",
"/api/artists/[id]/route": "app/api/artists/[id]/route.js",
"/aftercare/page": "app/aftercare/page.js",
"/api/admin/migrate/route": "app/api/admin/migrate/route.js",
"/api/artists/[id]/route": "app/api/artists/[id]/route.js",
"/api/auth/[...nextauth]/route": "app/api/auth/[...nextauth]/route.js",
"/artists/[id]/book/page": "app/artists/[id]/book/page.js",
"/artists/page": "app/artists/page.js",
"/artists/[id]/page": "app/artists/[id]/page.js",
"/auth/error/page": "app/auth/error/page.js",
"/auth/signin/page": "app/auth/signin/page.js",
"/artists/page": "app/artists/page.js",
"/book/page": "app/book/page.js",
"/contact/page": "app/contact/page.js",
"/gift-cards/page": "app/gift-cards/page.js",
"/deposit/page": "app/deposit/page.js",
"/favicon.ico/route": "app/favicon.ico/route.js",
"/auth/error/page": "app/auth/error/page.js",
"/auth/signin/page": "app/auth/signin/page.js",
"/gift-cards/page": "app/gift-cards/page.js",
"/page": "app/page.js",
"/specials/page": "app/specials/page.js",
"/privacy/page": "app/privacy/page.js",
"/favicon.ico/route": "app/favicon.ico/route.js",
"/terms/page": "app/terms/page.js",
"/api/admin/stats/route": "app/api/admin/stats/route.js",
"/api/artists/route": "app/api/artists/route.js",
"/api/files/bulk-delete/route": "app/api/files/bulk-delete/route.js",
"/api/files/folder/route": "app/api/files/folder/route.js",
"/api/artists/route": "app/api/artists/route.js",
"/api/files/route": "app/api/files/route.js",
"/api/files/stats/route": "app/api/files/stats/route.js",
"/api/appointments/route": "app/api/appointments/route.js",
"/api/portfolio/[id]/route": "app/api/portfolio/[id]/route.js",
"/api/portfolio/bulk-delete/route": "app/api/portfolio/bulk-delete/route.js",
"/api/portfolio/stats/route": "app/api/portfolio/stats/route.js",
"/api/appointments/route": "app/api/appointments/route.js",
"/api/files/stats/route": "app/api/files/stats/route.js",
"/api/files/route": "app/api/files/route.js",
"/api/portfolio/route": "app/api/portfolio/route.js",
"/api/portfolio/[id]/route": "app/api/portfolio/[id]/route.js",
"/api/users/route": "app/api/users/route.js",
"/api/upload/route": "app/api/upload/route.js",
"/api/settings/route": "app/api/settings/route.js",
"/admin/artists/page": "app/admin/artists/page.js",
"/admin/artists/new/page": "app/admin/artists/new/page.js",
"/api/users/route": "app/api/users/route.js",
"/admin/artists/[id]/page": "app/admin/artists/[id]/page.js",
"/admin/page": "app/admin/page.js",
"/admin/artists/new/page": "app/admin/artists/new/page.js",
"/admin/artists/page": "app/admin/artists/page.js",
"/admin/calendar/page": "app/admin/calendar/page.js",
"/admin/page": "app/admin/page.js",
"/admin/portfolio/page": "app/admin/portfolio/page.js",
"/admin/settings/page": "app/admin/settings/page.js",
"/admin/analytics/page": "app/admin/analytics/page.js",
"/admin/uploads/page": "app/admin/uploads/page.js"
"/admin/uploads/page": "app/admin/uploads/page.js",
"/admin/analytics/page": "app/admin/analytics/page.js"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,11 +17,11 @@
"wasm": [],
"assets": [],
"env": {
"__NEXT_BUILD_ID": "Wg86ttPbchbaXr-QgMl_6",
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "17081RAAyATuiotK5R7hh5isqj2lH5h4DOEGRt6U024=",
"__NEXT_PREVIEW_MODE_ID": "88d6565b640e48aee50946d953a2e540",
"__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "557cec8a7efe472f27a80fe51c78a386ecfc7bbaf73c297a8f9ad8988c889827",
"__NEXT_PREVIEW_MODE_SIGNING_KEY": "6942ce5017ba913dc3d9f31aa8c1b62cef0723664c92edea56b6aebbbeebc663"
"__NEXT_BUILD_ID": "moUXVtzs2Bhk2eoEY-sub",
"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "a5w8Al/LceGAEBAlCmLaVGy1uGjWkyVXJJF7qa6zvfU=",
"__NEXT_PREVIEW_MODE_ID": "357089a5a8857bac42c7016153480f60",
"__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "068ad86c40815751aaee20559bb2914de03a50bf0d7b9a358b02e3359699c8c9",
"__NEXT_PREVIEW_MODE_SIGNING_KEY": "7e9864116e15094f48442bf7a8975abc6d20440dc377454c5a4ba43bcf8dfed8"
}
}
},

View File

@ -1 +1 @@
{"node":{},"edge":{},"encryptionKey":"17081RAAyATuiotK5R7hh5isqj2lH5h4DOEGRt6U024="}
{"node":{},"edge":{},"encryptionKey":"a5w8Al/LceGAEBAlCmLaVGy1uGjWkyVXJJF7qa6zvfU="}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -673,12 +673,12 @@ var NEXT_DIR = path.join(__dirname, ".next");
var OPEN_NEXT_DIR = path.join(__dirname, ".open-next");
debug({ NEXT_DIR, OPEN_NEXT_DIR });
var NextConfig = { "env": {}, "webpack": null, "eslint": { "ignoreDuringBuilds": true }, "typescript": { "ignoreBuildErrors": true, "tsconfigPath": "tsconfig.json" }, "distDir": ".next", "cleanDistDir": true, "assetPrefix": "", "cacheMaxMemorySize": 52428800, "configOrigin": "next.config.mjs", "useFileSystemPublicRoutes": true, "generateEtags": true, "pageExtensions": ["tsx", "ts", "jsx", "js"], "poweredByHeader": true, "compress": true, "analyticsId": "", "images": { "deviceSizes": [640, 750, 828, 1080, 1200, 1920, 2048, 3840], "imageSizes": [16, 32, 48, 64, 96, 128, 256, 384], "path": "/_next/image", "loader": "default", "loaderFile": "", "domains": [], "disableStaticImages": false, "minimumCacheTTL": 60, "formats": ["image/webp"], "dangerouslyAllowSVG": false, "contentSecurityPolicy": "script-src 'none'; frame-src 'none'; sandbox;", "contentDispositionType": "inline", "remotePatterns": [], "unoptimized": true }, "devIndicators": { "buildActivity": true, "buildActivityPosition": "bottom-right" }, "onDemandEntries": { "maxInactiveAge": 6e4, "pagesBufferLength": 5 }, "amp": { "canonicalBase": "" }, "basePath": "", "sassOptions": {}, "trailingSlash": false, "i18n": null, "productionBrowserSourceMaps": false, "optimizeFonts": true, "excludeDefaultMomentLocales": true, "serverRuntimeConfig": {}, "publicRuntimeConfig": {}, "reactProductionProfiling": false, "reactStrictMode": null, "httpAgentOptions": { "keepAlive": true }, "outputFileTracing": true, "staticPageGenerationTimeout": 60, "swcMinify": true, "output": "standalone", "modularizeImports": { "@mui/icons-material": { "transform": "@mui/icons-material/{{member}}" }, "lodash": { "transform": "lodash/{{member}}" } }, "experimental": { "multiZoneDraftMode": false, "prerenderEarlyExit": false, "serverMinification": true, "serverSourceMaps": false, "linkNoTouchStart": false, "caseSensitiveRoutes": false, "clientRouterFilter": true, "clientRouterFilterRedirects": false, "fetchCacheKeyPrefix": "", "middlewarePrefetch": "flexible", "optimisticClientCache": true, "manualClientBasePath": false, "cpus": 11, "memoryBasedWorkersCount": false, "isrFlushToDisk": true, "workerThreads": false, "optimizeCss": false, "nextScriptWorkers": false, "scrollRestoration": false, "externalDir": false, "disableOptimizedLoading": false, "gzipSize": true, "craCompat": false, "esmExternals": true, "fullySpecified": false, "outputFileTracingRoot": "/home/Nicholai/Documents/Dev/united_v03/united-tattoo/united-tattoo", "swcTraceProfiling": false, "forceSwcTransforms": false, "largePageDataBytes": 128e3, "adjustFontFallbacks": false, "adjustFontFallbacksWithSizeAdjust": false, "typedRoutes": false, "instrumentationHook": false, "bundlePagesExternals": false, "parallelServerCompiles": false, "parallelServerBuildTraces": false, "ppr": false, "missingSuspenseWithCSRBailout": true, "optimizeServerReact": true, "useEarlyImport": false, "staleTimes": { "dynamic": 30, "static": 300 }, "optimizePackageImports": ["lucide-react", "date-fns", "lodash-es", "ramda", "antd", "react-bootstrap", "ahooks", "@ant-design/icons", "@headlessui/react", "@headlessui-float/react", "@heroicons/react/20/solid", "@heroicons/react/24/solid", "@heroicons/react/24/outline", "@visx/visx", "@tremor/react", "rxjs", "@mui/material", "@mui/icons-material", "recharts", "react-use", "@material-ui/core", "@material-ui/icons", "@tabler/icons-react", "mui-core", "react-icons/ai", "react-icons/bi", "react-icons/bs", "react-icons/cg", "react-icons/ci", "react-icons/di", "react-icons/fa", "react-icons/fa6", "react-icons/fc", "react-icons/fi", "react-icons/gi", "react-icons/go", "react-icons/gr", "react-icons/hi", "react-icons/hi2", "react-icons/im", "react-icons/io", "react-icons/io5", "react-icons/lia", "react-icons/lib", "react-icons/lu", "react-icons/md", "react-icons/pi", "react-icons/ri", "react-icons/rx", "react-icons/si", "react-icons/sl", "react-icons/tb", "react-icons/tfi", "react-icons/ti", "react-icons/vsc", "react-icons/wi"], "trustHostHeader": false, "isExperimentalCompile": false }, "configFileName": "next.config.mjs" };
var BuildId = "Wg86ttPbchbaXr-QgMl_6";
var BuildId = "moUXVtzs2Bhk2eoEY-sub";
var HtmlPages = [];
var RoutesManifest = { "basePath": "", "rewrites": { "beforeFiles": [], "afterFiles": [], "fallback": [] }, "redirects": [{ "source": "/:path+/", "destination": "/:path+", "internal": true, "statusCode": 308, "regex": "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$" }], "routes": { "static": [{ "page": "/", "regex": "^/(?:/)?$", "routeKeys": {}, "namedRegex": "^/(?:/)?$" }, { "page": "/_not-found", "regex": "^/_not\\-found(?:/)?$", "routeKeys": {}, "namedRegex": "^/_not\\-found(?:/)?$" }, { "page": "/admin", "regex": "^/admin(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin(?:/)?$" }, { "page": "/admin/analytics", "regex": "^/admin/analytics(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/analytics(?:/)?$" }, { "page": "/admin/artists", "regex": "^/admin/artists(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/artists(?:/)?$" }, { "page": "/admin/artists/new", "regex": "^/admin/artists/new(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/artists/new(?:/)?$" }, { "page": "/admin/calendar", "regex": "^/admin/calendar(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/calendar(?:/)?$" }, { "page": "/admin/portfolio", "regex": "^/admin/portfolio(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/portfolio(?:/)?$" }, { "page": "/admin/settings", "regex": "^/admin/settings(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/settings(?:/)?$" }, { "page": "/admin/uploads", "regex": "^/admin/uploads(?:/)?$", "routeKeys": {}, "namedRegex": "^/admin/uploads(?:/)?$" }, { "page": "/aftercare", "regex": "^/aftercare(?:/)?$", "routeKeys": {}, "namedRegex": "^/aftercare(?:/)?$" }, { "page": "/artists", "regex": "^/artists(?:/)?$", "routeKeys": {}, "namedRegex": "^/artists(?:/)?$" }, { "page": "/auth/error", "regex": "^/auth/error(?:/)?$", "routeKeys": {}, "namedRegex": "^/auth/error(?:/)?$" }, { "page": "/auth/signin", "regex": "^/auth/signin(?:/)?$", "routeKeys": {}, "namedRegex": "^/auth/signin(?:/)?$" }, { "page": "/book", "regex": "^/book(?:/)?$", "routeKeys": {}, "namedRegex": "^/book(?:/)?$" }, { "page": "/contact", "regex": "^/contact(?:/)?$", "routeKeys": {}, "namedRegex": "^/contact(?:/)?$" }, { "page": "/deposit", "regex": "^/deposit(?:/)?$", "routeKeys": {}, "namedRegex": "^/deposit(?:/)?$" }, { "page": "/favicon.ico", "regex": "^/favicon\\.ico(?:/)?$", "routeKeys": {}, "namedRegex": "^/favicon\\.ico(?:/)?$" }, { "page": "/gift-cards", "regex": "^/gift\\-cards(?:/)?$", "routeKeys": {}, "namedRegex": "^/gift\\-cards(?:/)?$" }, { "page": "/privacy", "regex": "^/privacy(?:/)?$", "routeKeys": {}, "namedRegex": "^/privacy(?:/)?$" }, { "page": "/specials", "regex": "^/specials(?:/)?$", "routeKeys": {}, "namedRegex": "^/specials(?:/)?$" }, { "page": "/terms", "regex": "^/terms(?:/)?$", "routeKeys": {}, "namedRegex": "^/terms(?:/)?$" }], "dynamic": [{ "page": "/admin/artists/[id]", "regex": "^/admin/artists/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/admin/artists/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/api/artists/[id]", "regex": "^/api/artists/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/api/artists/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/api/auth/[...nextauth]", "regex": "^/api/auth/(.+?)(?:/)?$", "routeKeys": { "nxtPnextauth": "nxtPnextauth" }, "namedRegex": "^/api/auth/(?<nxtPnextauth>.+?)(?:/)?$" }, { "page": "/api/portfolio/[id]", "regex": "^/api/portfolio/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/api/portfolio/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/artists/[id]", "regex": "^/artists/([^/]+?)(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/artists/(?<nxtPid>[^/]+?)(?:/)?$" }, { "page": "/artists/[id]/book", "regex": "^/artists/([^/]+?)/book(?:/)?$", "routeKeys": { "nxtPid": "nxtPid" }, "namedRegex": "^/artists/(?<nxtPid>[^/]+?)/book(?:/)?$" }], "data": { "static": [], "dynamic": [] } }, "locales": [] };
var MiddlewareManifest = { "version": 3, "middleware": { "/": { "files": ["server/edge-runtime-webpack.js", "server/middleware.js"], "name": "middleware", "page": "/", "matchers": [{ "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!_next\\/static|_next\\/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*))(.json)?[\\/#\\?]?$", "originalSource": "/((?!_next/static|_next/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*)" }], "wasm": [], "assets": [], "env": { "__NEXT_BUILD_ID": "Wg86ttPbchbaXr-QgMl_6", "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "17081RAAyATuiotK5R7hh5isqj2lH5h4DOEGRt6U024=", "__NEXT_PREVIEW_MODE_ID": "88d6565b640e48aee50946d953a2e540", "__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "557cec8a7efe472f27a80fe51c78a386ecfc7bbaf73c297a8f9ad8988c889827", "__NEXT_PREVIEW_MODE_SIGNING_KEY": "6942ce5017ba913dc3d9f31aa8c1b62cef0723664c92edea56b6aebbbeebc663" } } }, "functions": {}, "sortedMiddleware": ["/"] };
var AppPathRoutesManifest = { "/_not-found/page": "/_not-found", "/api/artists/[id]/route": "/api/artists/[id]", "/aftercare/page": "/aftercare", "/api/admin/migrate/route": "/api/admin/migrate", "/api/auth/[...nextauth]/route": "/api/auth/[...nextauth]", "/artists/[id]/book/page": "/artists/[id]/book", "/artists/page": "/artists", "/artists/[id]/page": "/artists/[id]", "/auth/error/page": "/auth/error", "/auth/signin/page": "/auth/signin", "/book/page": "/book", "/contact/page": "/contact", "/gift-cards/page": "/gift-cards", "/deposit/page": "/deposit", "/page": "/", "/specials/page": "/specials", "/privacy/page": "/privacy", "/favicon.ico/route": "/favicon.ico", "/terms/page": "/terms", "/api/admin/stats/route": "/api/admin/stats", "/api/files/bulk-delete/route": "/api/files/bulk-delete", "/api/files/folder/route": "/api/files/folder", "/api/artists/route": "/api/artists", "/api/portfolio/bulk-delete/route": "/api/portfolio/bulk-delete", "/api/portfolio/stats/route": "/api/portfolio/stats", "/api/appointments/route": "/api/appointments", "/api/files/stats/route": "/api/files/stats", "/api/files/route": "/api/files", "/api/portfolio/route": "/api/portfolio", "/api/portfolio/[id]/route": "/api/portfolio/[id]", "/api/users/route": "/api/users", "/api/upload/route": "/api/upload", "/api/settings/route": "/api/settings", "/admin/artists/page": "/admin/artists", "/admin/artists/new/page": "/admin/artists/new", "/admin/artists/[id]/page": "/admin/artists/[id]", "/admin/page": "/admin", "/admin/calendar/page": "/admin/calendar", "/admin/portfolio/page": "/admin/portfolio", "/admin/settings/page": "/admin/settings", "/admin/analytics/page": "/admin/analytics", "/admin/uploads/page": "/admin/uploads" };
var FunctionsConfigManifest = { "version": 1, "functions": { "/api/artists": {}, "/api/admin/stats": {}, "/api/files/folder": {}, "/api/files/bulk-delete": {}, "/api/files/stats": {}, "/api/files": {}, "/api/appointments": {}, "/api/portfolio/bulk-delete": {}, "/api/portfolio/stats": {}, "/api/portfolio/[id]": {}, "/api/portfolio": {}, "/api/settings": {}, "/api/users": {}, "/api/upload": {}, "/admin/settings": {}, "/admin/uploads": {}, "/admin/portfolio": {}, "/admin/analytics": {} } };
var MiddlewareManifest = { "version": 3, "middleware": { "/": { "files": ["server/edge-runtime-webpack.js", "server/middleware.js"], "name": "middleware", "page": "/", "matchers": [{ "regexp": "^(?:\\/(_next\\/data\\/[^/]{1,}))?(?:\\/((?!_next\\/static|_next\\/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*))(.json)?[\\/#\\?]?$", "originalSource": "/((?!_next/static|_next/image|favicon.ico|public|.*\\.png$|.*\\.jpg$|.*\\.jpeg$|.*\\.gif$|.*\\.svg$).*)" }], "wasm": [], "assets": [], "env": { "__NEXT_BUILD_ID": "moUXVtzs2Bhk2eoEY-sub", "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY": "a5w8Al/LceGAEBAlCmLaVGy1uGjWkyVXJJF7qa6zvfU=", "__NEXT_PREVIEW_MODE_ID": "357089a5a8857bac42c7016153480f60", "__NEXT_PREVIEW_MODE_ENCRYPTION_KEY": "068ad86c40815751aaee20559bb2914de03a50bf0d7b9a358b02e3359699c8c9", "__NEXT_PREVIEW_MODE_SIGNING_KEY": "7e9864116e15094f48442bf7a8975abc6d20440dc377454c5a4ba43bcf8dfed8" } } }, "functions": {}, "sortedMiddleware": ["/"] };
var AppPathRoutesManifest = { "/_not-found/page": "/_not-found", "/aftercare/page": "/aftercare", "/api/admin/migrate/route": "/api/admin/migrate", "/api/artists/[id]/route": "/api/artists/[id]", "/api/auth/[...nextauth]/route": "/api/auth/[...nextauth]", "/artists/[id]/book/page": "/artists/[id]/book", "/artists/[id]/page": "/artists/[id]", "/artists/page": "/artists", "/book/page": "/book", "/contact/page": "/contact", "/deposit/page": "/deposit", "/favicon.ico/route": "/favicon.ico", "/auth/error/page": "/auth/error", "/auth/signin/page": "/auth/signin", "/gift-cards/page": "/gift-cards", "/page": "/", "/specials/page": "/specials", "/privacy/page": "/privacy", "/terms/page": "/terms", "/api/admin/stats/route": "/api/admin/stats", "/api/artists/route": "/api/artists", "/api/files/bulk-delete/route": "/api/files/bulk-delete", "/api/files/folder/route": "/api/files/folder", "/api/files/route": "/api/files", "/api/files/stats/route": "/api/files/stats", "/api/appointments/route": "/api/appointments", "/api/portfolio/[id]/route": "/api/portfolio/[id]", "/api/portfolio/bulk-delete/route": "/api/portfolio/bulk-delete", "/api/portfolio/stats/route": "/api/portfolio/stats", "/api/portfolio/route": "/api/portfolio", "/api/upload/route": "/api/upload", "/api/settings/route": "/api/settings", "/api/users/route": "/api/users", "/admin/artists/[id]/page": "/admin/artists/[id]", "/admin/artists/new/page": "/admin/artists/new", "/admin/artists/page": "/admin/artists", "/admin/calendar/page": "/admin/calendar", "/admin/page": "/admin", "/admin/portfolio/page": "/admin/portfolio", "/admin/settings/page": "/admin/settings", "/admin/uploads/page": "/admin/uploads", "/admin/analytics/page": "/admin/analytics" };
var FunctionsConfigManifest = { "version": 1, "functions": { "/api/admin/stats": {}, "/api/artists": {}, "/api/files/folder": {}, "/api/files/bulk-delete": {}, "/api/files": {}, "/api/files/stats": {}, "/api/appointments": {}, "/api/portfolio/bulk-delete": {}, "/api/portfolio/[id]": {}, "/api/portfolio/stats": {}, "/api/upload": {}, "/api/users": {}, "/admin/portfolio": {}, "/admin/settings": {}, "/admin/uploads": {}, "/admin/analytics": {}, "/api/settings": {}, "/api/portfolio": {} } };
var PagesManifest = { "/_app": "pages/_app.js", "/_error": "pages/_error.js", "/_document": "pages/_document.js" };
process.env.NEXT_BUILD_ID = BuildId;

View File

@ -0,0 +1,76 @@
# UT-PUB-01 — ShadCN UI Consistency Across Pages
## Status
Draft
## Story
As a visitor,
I want the site to provide a consistent ShadCN-based UI across all pages,
so that navigation and interactions feel cohesive and predictable.
## Acceptance Criteria
1. Given any site page
When I navigate and interact
Then spacing, typography, components, and transitions are consistent
## Tasks / Subtasks
- [ ] Establish consistency audit across key pages (AC: 1)
- [ ] Review /aftercare, /deposit, /terms, /privacy, /book, home, and artist-related pages for spacing/typography/component variance
- [ ] Document variance list and map each to ShadCN primitive or composed component
- [ ] Standardize typography and spacing scales (AC: 1)
- [ ] Align to ShadCN/Tailwind scales per docs/ui-architecture.md “Styling Guidelines”
- [ ] Normalize heading/body sizes and leading across templates/layouts
- [ ] Replace/align components to ShadCN primitives where mismatched (AC: 1)
- [ ] Identify ad-hoc buttons/inputs/cards and replace with registry-aligned ui/* components
- [ ] Ensure variant management via cva() and class merging via cn()
- [ ] Ensure consistent page skeletons and boundaries (AC: 1)
- [ ] Provide/verify loading.tsx and error.tsx per key segments for consistent loading/error states
- [ ] Verify shared page section wrappers (components/layouts or shared) for paddings, max-width, and breakpoints
- [ ] Motion and transition alignment (AC: 1)
- [ ] Use tailwindcss-animate for subtle transitions; avoid custom inline animation styles
- [ ] Verify smooth scroll behavior is consistent where Lenis is used (if applicable to page)
- [ ] Tests and checks (AC: 1)
- [ ] Add RTL tests to verify consistent class patterns on representative pages/components
- [ ] Add visual acceptance notes for spacing/typography on critical templates
## Dev Notes
Pulled from project artifacts (do not invent):
- docs/ui-architecture.md
- Framework: Next.js 14 App Router with server components by default
- UI: ShadCN UI + Radix primitives; Tailwind v4 utilities; cva() variants and cn() class merge
- Styling Guidelines: Tailwind as primary styling, follow ShadCN spacing/typography tokens
- Routing: Provide loading.tsx and error.tsx for key segments; use route groups for separation
- Component Standards: Typed components, cva variants, consistent naming (kebab-case files, PascalCase components)
- Testing Requirements: Vitest + RTL for components; deterministic tests; mock external dependencies
- docs/PRD.md (Epic C — Public-Facing Website Experience)
- C1: All pages follow ShadCN baseline; unify typography, spacing, and components
- C2: Improve core public pages and ensure responsive, mobile-first behavior
- Existing Source Tree (reference only; verify before edits)
- app/ (App Router segments), components/ui/ (ShadCN primitives), components/* (composed/shared), styles/globals.css (Tailwind base)
### Testing (from docs/ui-architecture.md: Testing Requirements/Best Practices)
- Unit/Component: Vitest + RTL; Arrange-Act-Assert; deterministic; mock router/network
- Structure: tests under __tests__/ with component and integration coverage
- Goals: Verify applied class patterns for typographic scale and spacing, and presence of ShadCN primitives/variants in representative pages
## Change Log
| Date | Version | Description | Author |
|------------|---------|---------------------------------------------|----------------|
| 2025-09-19 | 0.1 | Initial draft of PUB-01 story | Scrum Master |
## Dev Agent Record
### Agent Model Used
<!-- dev-agent: record model/version used during implementation -->
### Debug Log References
<!-- dev-agent: link to any debug logs or traces generated -->
### Completion Notes List
<!-- dev-agent: notes about completion, issues encountered, resolutions -->
### File List
<!-- dev-agent: list all files created/modified/affected during implementation -->
## QA Results
<!-- qa-agent: append review results and gate decision here -->

View File

@ -0,0 +1,80 @@
# UT-PUB-02 — Parallax and Split-Screen Hero Sections
## Status
Draft
## Story
As a visitor,
I want smooth, layered parallax and splitscreen hero sections on key pages,
so that the site feels immersive and visually engaging without sacrificing performance or accessibility.
## Acceptance Criteria
1. Given Im on the homepage or an artist page
When I scroll
Then layered visuals and split sections animate smoothly within performance budgets (no noticeable jank; respects prefersreducedmotion)
## Tasks / Subtasks
- [ ] Define UX and constraints (AC: 1)
- [ ] Specify max parallax depth, layers, and scroll ranges (mobile/desktop)
- [ ] Document fallback behavior for `prefers-reduced-motion: reduce` (animations disabled or simplified)
- [ ] Establish performance budgets: LCP target, avoid layout shift, minimal main thread cost
- [ ] Implement homepage hero enhancements (AC: 1)
- [ ] Update/extend `components/hero-section.tsx` for layered composition (foreground text, midground overlays, background image)
- [ ] Use CSS transforms and opacity for motion; avoid heavy JS; throttle with requestAnimationFrame
- [ ] Guard with `use client` only where needed; ensure SSR compatibility for static layers
- [ ] Implement artist page splitscreen/hero (AC: 1)
- [ ] Add or update hero for artist pages (e.g., `components/artists-page-section.tsx` / `components/artist-portfolio.tsx`) to support splitscreen layout (image/story)
- [ ] Ensure composition adapts at breakpoints (stack on mobile, split at md+)
- [ ] Motion system & accessibility (AC: 1)
- [ ] Respect `prefers-reduced-motion`; expose CSS class or data attribute to disable animations
- [ ] Use `tailwindcss-animate` classes for subtle transitions; avoid inline animation CSS
- [ ] Ensure focus order and headings are unaffected by decorative layers (decorative images `aria-hidden`)
- [ ] Smooth scrolling integration (AC: 1)
- [ ] If Lenis is enabled, verify no conflict with parallax updates (no double scroll handlers)
- [ ] Disable or degrade parallax effect when smooth scroll is off or reduced motion is on
- [ ] Performance validation (AC: 1)
- [ ] Audit LCP/INP locally; ensure no long tasks > 50ms introduced by parallax logic
- [ ] Validate no layout shift (CLS) from parallax layers; use fixed heights/aspectratio placeholders
- [ ] Tests and checks (AC: 1)
- [ ] RTL tests validate presence of hero layers and reducedmotion fallback class toggles
- [ ] Add visual acceptance notes and manual test plan for scroll behavior across sm/md/lg
## Dev Notes
Pulled from project artifacts (do not invent):
- docs/PRD.md (Epic C — Public Website)
- UTPUB02: Smooth, performant parallax/splitscreen hero on homepage and artist pages
- Visual emphasis with highquality photography; mobilefirst responsiveness (C1C3)
- docs/ui-architecture.md
- Use Tailwind v4 utilities; ShadCN/Radix for a11y components; use `tailwindcss-animate` and Lenis; avoid heavy JS animation libs
- Accessibility baseline WCAG AA; focus visible; avoid keyboard traps; keep components presentational with sideeffects in hooks
- Performance: prefer server components; client JS only when necessary; lazyload heavy modules
- Existing Source Tree (verify before edits)
- `components/hero-section.tsx` (homepage hero)
- `components/artists-page-section.tsx`, `components/artist-portfolio.tsx` (artist views)
- `components/section-header.tsx`, `components/smooth-scroll-provider.tsx` (potential integration points)
### Testing (from docs/ui-architecture.md: Testing Requirements/Best Practices)
- Unit/Component: Vitest + RTL; deterministic; verify reducedmotion behavior and layer presence
- E2E (later): confirm smooth scroll and no jank on critical scroll ranges in preview env
- Targets: No CLS from hero; initial render stable with defined heights/placeholders
## Change Log
| Date | Version | Description | Author |
|------------|---------|-----------------------------------------------|--------------|
| 2025-09-19 | 0.1 | Initial draft of PUB02 story | Scrum Master |
## Dev Agent Record
### Agent Model Used
<!-- dev-agent: record model/version used during implementation -->
### Debug Log References
<!-- dev-agent: link to any debug logs or traces generated -->
### Completion Notes List
<!-- dev-agent: notes about completion, issues encountered, resolutions -->
### File List
<!-- dev-agent: list all files created/modified/affected during implementation -->
## QA Results
<!-- qa-agent: append review results and gate decision here -->

View File

@ -0,0 +1,84 @@
# UT-PUB-03 — Search Page with Filters (Style, Availability, Price Tier)
## Status
Draft
## Story
As a visitor,
I want a dedicated search page with filters for style, availability, and price tier,
so that I can quickly find relevant artists and content that match my preferences.
## Acceptance Criteria
1. Given Im on /search
When I apply filters (style, availability, price tier)
Then artist and content results update accordingly
## Tasks / Subtasks
- [ ] Define information architecture and UX (AC: 1)
- [ ] Specify URL and route location (e.g., `app/(marketing)/search/page.tsx`)
- [ ] Determine filter controls: Style (multi-select), Availability (toggle/range), Price Tier (segmented or select)
- [ ] Document a11y labels, roles, and keyboard interactions for all controls
- [ ] Implement filter UI using ShadCN primitives (AC: 1)
- [ ] Style filter: multi-select (e.g., `Command`, `Popover`, `Checkbox`) or `Combobox`
- [ ] Availability filter: switch/toggle or date-range stub; annotate as UIonly if backend is pending
- [ ] Price tier: `Select` or segmented controls; describe tiers in helper text
- [ ] Provide a clear “Reset filters” action and active filter chips summary
- [ ] Results panel & empty/loading states (AC: 1)
- [ ] Create results list component (artists first; content secondary if present)
- [ ] Provide `loading` state skeletons and `empty` state messaging with guidance
- [ ] Ensure responsive layout (stack on mobile; two-column at md+ if space allows)
- [ ] Wiring strategy (frontend scope) (AC: 1)
- [ ] Implement client-side filter state (Zustand or component state) with URL sync via search params
- [ ] Stub data source: use existing `data/artists.ts` and extend shape locally if needed (no DB access)
- [ ] Add filtering utilities (pure functions) to filter by style/availability/price tier
- [ ] Accessibility & usability (AC: 1)
- [ ] Proper labels and `aria-describedby` for controls; visible focus states
- [ ] Keyboard navigation for opening/closing filter popovers and selecting items
- [ ] Announce result counts with `aria-live="polite"` when filters change
- [ ] Performance & UX polish (AC: 1)
- [ ] Debounce filter updates where typing involved; avoid layout shift
- [ ] Progressive loading placeholders; ensure images use Next `<Image>` with defined sizes
- [ ] Tests and checks (AC: 1)
- [ ] RTL tests: applying filters updates visible results; reset clears filters; a11y attributes present
- [ ] Snapshot or DOM assertions for loading/empty states
- [ ] Basic URL param sync test to preserve state on reload/back
## Dev Notes
Pulled from project artifacts (do not invent):
- docs/PRD.md (Epic C — Public Website)
- UTPUB03: Dedicated search page with filters (style, availability, price tier); results update accordingly
- C1C3: ShadCN baseline; consistent navigation/responsiveness; discovery improvements
- docs/ui-architecture.md
- Use ShadCN/Radix primitives; Tailwind v4; cva() variants and cn() for classes
- Accessibility: WCAG AA; labeled controls; visible focus; keyboard support
- Performance: prefer server comps; client JS only when needed; lazyload heavy modules
- Existing Source Tree (verify before edits)
- `data/artists.ts` (baseline data for local filtering)
- `components/section-header.tsx`, `components/artists-grid.tsx`, `components/artist-portfolio.tsx`
- `components/ui/*` for primitives; `lib/utils.ts` for cn()
### Testing (from docs/ui-architecture.md: Testing Requirements/Best Practices)
- Unit/Component (Vitest + RTL): filter components and state logic
- Integration: page-level tests verifying URL param sync and results updates
- Accessibility: presence of labels, roles, keyboard navigation; live region announcer for counts
## Change Log
| Date | Version | Description | Author |
|------------|---------|----------------------------------------------|--------------|
| 2025-09-19 | 0.1 | Initial draft of PUB03 story | Scrum Master |
## Dev Agent Record
### Agent Model Used
<!-- dev-agent: record model/version used during implementation -->
### Debug Log References
<!-- dev-agent: link to any debug logs or traces generated -->
### Completion Notes List
<!-- dev-agent: notes about completion, issues encountered, resolutions -->
### File List
<!-- dev-agent: list all files created/modified/affected during implementation -->
## QA Results
<!-- qa-agent: append review results and gate decision here -->

View File

@ -0,0 +1,83 @@
# UT-PUB-04 — Quick Search (Ctrl+K) Across Artists and Content
## Status
Draft
## Story
As a visitor,
I want a quick search palette I can open with Ctrl+K to find artists and educational content,
so that I can rapidly navigate to the most relevant pages without leaving my current context.
## Acceptance Criteria
1. Given I press Ctrl+K (or Cmd+K on macOS)
When the search dialog opens and I type a query
Then I see navigable results for artists and key content pages, and can navigate via keyboard (Enter) or mouse
## Tasks / Subtasks
- [ ] Define UX behavior and scope (AC: 1)
- [ ] Invocation: keyboard (Ctrl/Cmd+K), header button, and accessible “Open search” control
- [ ] Result groups: Artists first, then content (Aftercare, Specials, Terms, Privacy, Booking, etc.)
- [ ] Empty and loading states; close behavior (Esc/click outside); responsive treatment
- [ ] Implement command palette UI using ShadCN primitives (AC: 1)
- [ ] Base on `Command` + `Dialog` (or `Popover`) primitives with a labeled input
- [ ] Result items show title, type (artist/content), and optional subtitle (style)
- [ ] Keyboard navigation: Up/Down to move, Enter to go, Esc to close; focus trap enabled
- [ ] Data sources and matching (AC: 1)
- [ ] Artists: use `data/artists.ts` (name, styles, slug) for local search
- [ ] Content: seed a static list of key routes with titles and tags (e.g., Aftercare, Deposit, Book, Privacy, Terms, Specials, Gift Cards, Contact)
- [ ] Implement lightweight fuzzy/contains matching util; highlight matches (optional)
- [ ] Routing and integration (AC: 1)
- [ ] Navigate to selected result via Next.js Link or router; close palette after navigation
- [ ] Integrate trigger in `components/navigation.tsx` or site header
- [ ] Support deep links (e.g., /artists/[slug])
- [ ] Accessibility (AC: 1)
- [ ] Input has accessible name; results container has appropriate role
- [ ] Live region optionally announces result count updates (`aria-live="polite"`)
- [ ] Ensure focus is returned to the trigger when palette closes
- [ ] Performance and UX polish (AC: 1)
- [ ] Debounce input; avoid layout shift; keep main thread work minimal
- [ ] Ensure reduced motion users get no distracting transitions
- [ ] Tests and checks (AC: 1)
- [ ] RTL tests: open with keyboard shortcut, type to filter, select with Enter, Esc to close
- [ ] Verify a11y attributes: labels, focus trapping, screen reader announcements
- [ ] Snapshot or DOM assertions for empty/loading states
## Dev Notes
Pulled from project artifacts (do not invent):
- docs/PRD.md (Epic C — Public Website)
- UTPUB04: Quick search (Ctrl+K) to find artists and educational content; navigable results
- C1C3: ShadCN baseline; consistent navigation/responsiveness; discovery improvements
- docs/ui-architecture.md
- Use ShadCN/Radix primitives; Tailwind v4; cva() and cn()
- Accessibility: WCAG AA, labeled inputs, visible focus, keyboard support, focus management
- Performance: client JS only where necessary; keep logic lightweight; lazyload heavy parts
- Existing Source Tree (verify before edits)
- `data/artists.ts` for local search source
- `components/navigation.tsx` potential trigger location
- `components/ui/*` primitives (Command, Dialog), `lib/utils.ts` for cn()
### Testing (from docs/ui-architecture.md: Testing Requirements/Best Practices)
- Unit/Component (Vitest + RTL): command palette open/close, input filtering, keyboard navigation
- Accessibility: role/label presence, focus trap, ESC close, return focus to trigger
- Integration: navigation to selected result updates the URL and closes the palette
## Change Log
| Date | Version | Description | Author |
|------------|---------|----------------------------------------------|--------------|
| 2025-09-19 | 0.1 | Initial draft of PUB04 story | Scrum Master |
## Dev Agent Record
### Agent Model Used
<!-- dev-agent: record model/version used during implementation -->
### Debug Log References
<!-- dev-agent: link to any debug logs or traces generated -->
### Completion Notes List
<!-- dev-agent: notes about completion, issues encountered, resolutions -->
### File List
<!-- dev-agent: list all files created/modified/affected during implementation -->
## QA Results
<!-- qa-agent: append review results and gate decision here -->

View File

@ -0,0 +1,78 @@
# UT-PUB-05 — Aftercare Enhancements (Visuals, Progress, Printable/PDF)
## Status
Draft
## Story
As a visitor,
I want an improved aftercare page with visuals, progress tracking, and checklists,
so that I can follow care steps easily and save/print them for reference.
## Acceptance Criteria
1. Given I open /aftercare
When I read and mark steps
Then my progress is saved locally and content is printable/PDFdownloadable
## Tasks / Subtasks
- [ ] Define IA/UX and a11y (AC: 1)
- [ ] Structure the page into sections (e.g., Day 01, Days 27, Weeks 2+) with clear headings and step lists
- [ ] Provide alttext for visuals/diagrams; use `aria-describedby` for any step notes
- [ ] Keyboard navigation order verified; focus states visible
- [ ] Implement checklists with local persistence (AC: 1)
- [ ] Use ShadCN primitives for check items (`Checkbox`, `Label`, `Card`/`Accordion` as needed)
- [ ] Persist state to `localStorage` keyed by versioned slug (e.g., `aftercare:v1`)
- [ ] Add “Reset progress” control (with confirm) and incremental autosave
- [ ] Visuals and media (AC: 1)
- [ ] Integrate representative images/diagrams (from `public/`), marked decorative as appropriate (`aria-hidden`) or described in text
- [ ] Ensure images use Next `<Image>` with defined sizes/aspect ratio to avoid CLS
- [ ] Printable/PDF output (AC: 1)
- [ ] Add a print stylesheet: hides nav/interactive chrome, shows checklist states
- [ ] Provide “Print / Save as PDF” button (invokes `window.print()`); note PDF export in help text
- [ ] Ensure color contrast and typography meet WCAG AA in print mode
- [ ] Empty/error/reducedmotion states (AC: 1)
- [ ] Provide simple skeletons for image blocks
- [ ] Respect `prefers-reduced-motion` and avoid distracting animations
- [ ] Tests and checks (AC: 1)
- [ ] RTL tests: checking a step persists after reload; reset clears state
- [ ] Verify print button renders and calls `window.print()` (mocked)
- [ ] Basic accessibility assertions: labels for checkboxes, headings structure, contrast tokens
## Dev Notes
Pulled from project artifacts (do not invent):
- docs/PRD.md (Epic C — Public Website)
- UTPUB05: Aftercare page with visuals, progress tracking, checklists; printable/PDF
- C1C3: ShadCN baseline; responsive/mobilefirst; consistent navigation
- docs/ui-architecture.md
- Use ShadCN/Radix primitives; Tailwind v4; cva() variants and `cn()` merge
- Accessibility: WCAG AA, labels, focus, keyboard support; presentational components preferred
- Performance: server comps where possible; client JS only for interactivity; avoid CLS with defined sizes
- Existing Source Tree (verify before edits)
- `components/aftercare-page.tsx` (page composition)
- `app/aftercare/` segment for route
- `components/section-header.tsx`, `components/ui/*` primitives; `styles/globals.css`
### Testing (from docs/ui-architecture.md: Testing Requirements/Best Practices)
- Unit/Component (Vitest + RTL): checklist toggling, localStorage persistence, reset flow
- Integration: rendering within `app/aftercare/page.tsx` and print button behavior
- a11y: checkbox labeling, heading outline, focus indicators, print contrast
## Change Log
| Date | Version | Description | Author |
|------------|---------|-----------------------------------------------|--------------|
| 2025-09-19 | 0.1 | Initial draft of PUB05 story | Scrum Master |
## Dev Agent Record
### Agent Model Used
<!-- dev-agent: record model/version used during implementation -->
### Debug Log References
<!-- dev-agent: link to any debug logs or traces generated -->
### Completion Notes List
<!-- dev-agent: notes about completion, issues encountered, resolutions -->
### File List
<!-- dev-agent: list all files created/modified/affected during implementation -->
## QA Results
<!-- qa-agent: append review results and gate decision here -->

View File

@ -0,0 +1,96 @@
# UT-PUB-06 — Artist Galleries with Style Filters and Lightbox
## Status
Draft
## Story
As a visitor,
I want to browse artist galleries with style-based filtering and interactive zoom/lightbox,
so that I can quickly explore relevant work and inspect pieces without layout shifts.
## Acceptance Criteria
1. Given Im on an artist page
When I filter by style or click an image
Then the gallery updates, and I can zoom without layout shift
## Tasks / Subtasks
- [ ] Define IA/UX and behavior (AC: 1)
- [ ] Decide filter control pattern: style chips (multi-select) vs. tabs (single/multi) with clear active state
- [ ] Provide an “All styles” default and a “Clear filters” action with keyboard support
- [ ] Grid layout responsive spec (e.g., 2 cols sm, 3 cols md, 4 cols lg) with consistent gaps and aspect ratios
- [ ] Implement style filters using ShadCN primitives (AC: 1)
- [ ] Build filter controls with `Badge`/`Toggle`/`Checkbox` + `Popover` or `Tabs` (consistent with DS)
- [ ] Ensure accessible names for controls and selection state (aria-pressed/aria-checked as appropriate)
- [ ] Optional: sync selected styles to URL search params to preserve state on reload/back
- [ ] Gallery grid with CLS-safe images (AC: 1)
- [ ] Use Next `<Image>` with explicit width/height or `sizes` + aspect-ratio wrappers to prevent CLS
- [ ] Lazy-load and use blur or LQIP placeholders for progressive loading
- [ ] Support client-only fallback where required while keeping server components where possible
- [ ] Lightbox / zoom experience (AC: 1)
- [ ] Implement lightbox with ShadCN `Dialog` (or `Sheet`) composition: open on image click; focus trap; Esc closes; overlay click closes
- [ ] Provide keyboard navigation for next/prev (←/→) and close (Esc); visible focus for controls
- [ ] Add basic zoom controls (+//fit) or at minimum a full-bleed modal image with proper alt text
- [ ] Ensure images are marked decorative (`aria-hidden`) in grid when redundant with captions; modal has accessible name/description
- [ ] Empty/loading/error states (AC: 1)
- [ ] Loading skeletons for grid; empty state messaging for no matching styles (with clear filters action)
- [ ] Reduced motion supported; minimize distracting transitions; respect `prefers-reduced-motion`
- [ ] Performance checks (AC: 1)
- [ ] Validate no layout shift on open/close or image load; pre-allocate modal dimensions or use aspect-ratio containers
- [ ] Avoid long tasks > 50ms during navigation/zoom; throttle handlers; only minimal client JS in modal
- [ ] Tests and checks (AC: 1)
- [ ] RTL tests: filtering updates visible thumbnails; clicking opens modal; Esc closes; arrow keys navigate
- [ ] A11y assertions: labels/roles, focus-trap, return focus to trigger on close, live region (optional) for image count
- [ ] Snapshot/DOM assertions for grid structure (classes for gap/cols/aspect) and empty/loading states
## Dev Notes
Pulled from project artifacts (do not invent):
- docs/PRD.md (Epic C — Public Website)
- UTPUB06: Browse artist galleries with style-based filtering and interactive zoom/lightbox; no layout shift
- C1C3: ShadCN baseline; responsive behavior; smooth/consistent navigation
- docs/ui-architecture.md
- Use ShadCN/Radix primitives; Tailwind v4; `cva()` variants + `cn()` merge
- Accessibility: WCAG AA, labeled controls, keyboard navigation, focus management, avoid traps
- Performance: server components where sensible; client JS only for interactivity; define sizes to prevent CLS; lazy-load heavy modules
- Existing Source Tree (verify before edits)
- `components/artist-portfolio.tsx` (artist gallery composition)
- `components/artists-grid.tsx` (grid listing for artists/cards)
- `data/artists.ts` (source of artist/portfolio metadata; extend locally for styles if needed)
- `components/ui/*` primitives (Dialog, Badge/Toggle/Checkbox, Tabs), `lib/utils.ts` (`cn`)
### Implementation Hints
- Filters:
- Consider `Tabs` for mutually exclusive primary style with optional “All” and `Popover`/`Command` for multi-select advanced filter
- Active filter chips summary above the grid (dismissible chips)
- Lightbox:
- Use `Dialog` with `DialogContent` set to `max-w-[calc(100vw-2rem)]` and responsive `h-[80vh]` container; image inside `object-contain`
- Keyboard handlers in a small hook; ensure focus returns to last clicked thumbnail on close
- Image handling:
- Provide `sizes` for breakpoints (e.g., `(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw`)
- Use `placeholder="blur"` (or custom low-res) to improve perceived performance
### Testing (from docs/ui-architecture.md: Testing Requirements/Best Practices)
- Unit/Component: filter logic utilities; dialog open/close; arrow key handlers; focus return
- Integration: page-level tests on an example artist route verifying filter + modal behavior
- Accessibility: roles/labels, keyboard navigation (Tab/Shift+Tab, arrows, Esc), visible focus
- Performance: assert that `img` elements have width/height or parent aspect-ratio to avoid CLS
## Change Log
| Date | Version | Description | Author |
|------------|---------|-----------------------------------------------|--------------|
| 2025-09-19 | 0.1 | Initial draft of PUB06 story | Scrum Master |
## Dev Agent Record
### Agent Model Used
<!-- dev-agent: record model/version used during implementation -->
### Debug Log References
<!-- dev-agent: link to any debug logs or traces generated -->
### Completion Notes List
<!-- dev-agent: notes about completion, issues encountered, resolutions -->
### File List
<!-- dev-agent: list all files created/modified/affected during implementation -->
## QA Results
<!-- qa-agent: append review results and gate decision here -->