Compare commits

...

3 Commits

Author SHA1 Message Date
90e730c2fe coding agent adjustments 2025-10-23 03:06:16 -06:00
a06b2607c7 feat(video): add custom reel player with local mp4 support
Replaced Frame.io link with embedded local video player for the studio reel.

## Changes
- Created ReelPlayer component with custom HTML5 video controls
  - Play/pause, volume, fullscreen, progress bar with scrubbing
  - Loading and error states with user-friendly messages
  - Dark theme styling with orange (#ff4d00) accents and sharp corners
  - Responsive design for mobile/tablet/desktop

- Integrated ReelPlayer into Temp-Placeholder (Work section)
  - Replaced external Frame.io link with local /reel.mp4
  - Maintains minimal aesthetic with proper animations

- Fixed middleware whitelist issue
  - Added /reel.mp4 to middleware allowlist (src/middleware.ts:8)
  - Prevents 307 redirect that was causing "text/html" Content-Type error

- Added video file headers to next.config.ts
  - Ensures proper video/mp4 MIME type for all .mp4 files

- Updated CLAUDE.md documentation
  - Added critical warning about middleware whitelist in "Common pitfalls"
  - Added rule #9 to "Agents operating rules" for public/ file additions
  - Future-proofs against this issue happening again

## Technical Details
- Video: 146MB, H.264 codec, 4K resolution (3840x2160)
- Player handles large file buffering gracefully
- ReadyState check prevents loading overlay persistence
- All controls accessible and keyboard-friendly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 03:05:27 -06:00
bedd355b78 refactor(ui): transform temp-placeholder into minimal card layout
- Removed gradient border wrapper and CursorDotBackground
- Created centered dark card (#0a0a0a) on black background with subtle border
- Added minimal navigation with smooth scroll to sections
- Organized content into sections (about, work, studio, contact)
- Added > prefixes to section headers for terminal aesthetic
- Removed all glow effects and rounded corners
- Kept only orange (#ff4d00), white, and black color palette
- Preserved all Easter eggs (depth map, pigeon zone, video previews)
- Added smooth scroll behavior to globals.css

Inspired by minimal editorial layouts while maintaining the site's humor and personality.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-23 02:09:48 -06:00
9 changed files with 958 additions and 290 deletions

227
AGENTS.md
View File

@ -1,55 +1,206 @@
# AGENTS.md
# Agents Guide (Single Source of Truth)
This file provides guidance to Opencode when working with code in this repository.
This document is the canonical reference for humans and `Agents` working in this repository. It standardizes stack, structure, workflows, and guardrails so contributions stay consistent and safe.
## Repository Snapshot
## 1) Scope and goals
TypeScript + React 19 (Next.js 15.5.4) site builder for Biohazard VFX studio. Single npm workspace. Deployed to Cloudflare Workers via OpenNext. ESLint + TypeScript strict mode enforced pre-commit. No automated tests; manual QA in Chromium/Safari only. (Sources: package.json, tsconfig.json, README.md)
* Make onboarding fast with a single place to look
* Define conventions so changes are predictable
* Provide exact commands that `Agents` can run without guesswork
* Prevent accidental regressions in routing, theming, SEO, and deployment
## Commands
## 2) Tech stack
| Task | Command | Scope | Source |
|------|---------|-------|--------|
| Dev server | `npm run dev` | Root | package.json:6 |
| Build (Next.js) | `npm run build` | Root | package.json:7 |
| Build (Cloudflare Workers) | `npm run build:open-next` | Root | package.json:8 |
| Lint (ESLint) | `npm run lint` | Root | package.json:10 |
| Type check (TypeScript) | `npm run type-check` | Root | package.json:11 |
| Start production server | `npm run start` | Root | package.json:9 |
* **Framework**: Next.js 15.5.4, React 19, TypeScript
* **Styling**: Tailwind CSS 4, shadcn/ui
* **Animation**: Framer Motion
* **Forms**: react-hook-form + Zod
* **Platform**: Cloudflare Workers via OpenNext
* **Package manager**: npm
* **Node**: LTS 20 or 22
**Run a single test:** Not applicable; no automated test runner configured.
## 3) Project layout
## Architecture Overview
```
root
├─ src/
│ ├─ app/ # App Router pages and layouts
│ │ ├─ (marketing)/ # Example route groups
│ │ ├─ api/ # Route handlers
│ │ └─ layout.tsx # Root layout, see rules below
│ ├─ components/ # Reusable UI
│ ├─ data/ # JSON or TS data objects consumed by pages
│ ├─ lib/ # Utilities, hooks, server actions
│ ├─ styles/ # globals.css, tailwind utilities if applicable
│ └─ types/ # Shared types
├─ public/ # Static assets
├─ next.config.ts
├─ tailwind.config.ts
├─ wrangler.toml # Cloudflare deploy config
└─ package.json
```
- **Entry point**: `src/app/layout.tsx` (root layout with Navigation & Footer); app routes in `src/app/{route}/page.tsx` (about, contact, portfolio, services). (Source: README.md:6169, src/app/layout.tsx)
- **Components**: Functional React + Radix UI via shadcn/ui. Primitives in `src/components/ui/`; feature components in `src/components/`. (Source: README.md:7079, package.json:1624)
- **Data & utils**: Project and service metadata in `src/data/projects.ts` and `src/data/services.ts`; shared utilities in `src/lib/utils.ts`. (Source: README.md:8084)
- **Styling**: Tailwind CSS 4 + CSS variables in `src/app/globals.css`; components use `clsx` + `tailwind-merge` for conditional styles. (Source: tsconfig.json, package.json:39)
- **Forms**: `react-hook-form` + Zod validation; e.g., MultiStepForm (contact page). (Source: package.json:1415, 33, 35)
- **External services**: Instagram Feed component integrates Instagram API via `src/lib/instagram-api.ts`. Remote images from unsplash.com. (Source: next.config.ts:713, src/lib/)
- **Deployment**: OpenNext adapter for Cloudflare Workers. Build output in `.open-next/` (worker.js + assets). Domain routes in wrangler.toml. (Source: wrangler.toml, open-next.config.ts, next.config.ts:1622)
### Import aliases
## Conventions and Rules
* Prefer absolute imports using `@` mapped to `src` via `tsconfig.json` paths.
From CONTRIBUTING.md:
## 4) Authoritative UI system
- **Commit format**: conventional commits (`feat(scope): subject`). Atomic commits. Examples: `feat(portfolio): add project filtering`, `fix(header): resolve mobile menu overflow`. (CONTRIBUTING.md:122147)
- **Branch strategy**: `main` (production), `develop` (integration), `feature/*`, `fix/*`, `hotfix/*`. Always create from `develop`. (CONTRIBUTING.md:4954, 5762)
- **React/Next.js**: Server components by default; client-only where needed. Single responsibility per component. Keep custom CSS minimal. (CONTRIBUTING.md:8092)
- **TypeScript**: Avoid `any`. Define interfaces for props and API responses. (CONTRIBUTING.md:7478)
* **Theme**: dark mode is the default. Do not introduce light-first designs without approval.
* **Typography**: default to Geist and Geist Mono via CSS variables. If adding a new font, add it as a variable and document it here before use.
* **Components**: use shadcn/ui primitives. Extend with local wrappers placed in `src/components/ui/`.
* **Spacing and rhythm**: follow Tailwind 4 defaults. Prefer utility classes over custom CSS unless componentized.
* **Animation**: keep motion subtle. Framer Motion only for meaningful transitions.
## CI Hooks That Matter Locally
## 5) Routing and layout rules
From `.gitea/workflows/ci.yml`:
* The **root layout** owns global providers, theme class, `<Nav />`, and `<Footer />`. Do not duplicate these in child layouts.
* Pages live in `src/app`. Keep server components as the default. Promote to client component only when needed.
* Metadata must be defined per route with the Next.js Metadata API.
## 6) SEO and metadata
* Use the Metadata API for title, description, Open Graph, and Twitter cards.
* Add structured data with JSON-LD in the root layout or specific routes when required.
* All pages must render a unique `title` and `description` suitable for indexing.
## 7) Forms and validation
* Use `react-hook-form` with Zod schemas.
* Surface field-level errors and a generic submit error. Never swallow validation errors.
## 8) Images and assets
* Use Next Image component for remote images.
* If a new external domain is introduced, add it to `next.config.ts` remote patterns and document it here.
* Keep `public/` for truly static assets only.
## 9) Environment variables
Provide a `.env.sample` and keep it in sync. Typical keys:
```
NEXT_PUBLIC_SITE_URL=
RESEND_API_KEY=
CF_PAGES_URL=
```
Do not commit real secrets. `Agents` must fail a task rather than hardcode a secret.
## 10) Local development
```
# install
npm ci
# run dev server
npm run dev
# type checks and linting
npm run typecheck
npm run lint
# build and preview
npm run build
npm run start
```
Notes
* The Next build may be configured to ignore ESLint and TS errors for production bundling speed. CI still gates on `lint` and `typecheck` before merge.
## 11) Deployment on Cloudflare Workers with OpenNext
### Required wrangler.toml settings
```
name = "site-worker"
main = ".open-next/worker/index.mjs"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
assets = { directory = ".open-next/assets" }
```
### Build and deploy
```
# produce OpenNext build artifacts
npx open-next@latest build
# deploy worker and assets
npx wrangler deploy .open-next/worker
```
Guidelines
* Always run `npm run typecheck` and `npm run lint` before build.
* Ensure `assets.directory` matches the OpenNext output.
* Keep the compatibility date at or after 2024-09-23.
## 12) Branching, commits, and CI
* **Default branch**: `main` is protected.
* **Workflow**: feature branches -> PR -> required checks -> squash merge.
* **Commit format**: Conventional Commits. Examples
* `feat: add contact form schema`
* `fix: correct Image remote pattern`
* `chore: bump dependencies`
* **Required checks**
* `npm run lint`
* `npm run typecheck`
* `npm run build` can be optional locally if CI runs it, but must succeed before deploy.
## 13) Testing
* **Unit**: place tests close to sources, name with `.test.ts` or `.test.tsx`.
* **E2E**: optional Playwright. If used, add a `playwright.config.ts` and a `npm run e2e` script.
## 14) Data and content
* Non-secret content belongs in `src/data` as TS modules or JSON. Keep it presentation-agnostic.
* Do not fetch static project data in client components. Prefer server components or file imports.
## 15) `Agents` operating rules
1. Read this guide before making changes.
2. Before adding a dependency, justify it in the PR description and update this document if it affects conventions.
3. When creating pages, set Metadata and verify unique title and description.
4. If a build ignores ESLint or TS errors, CI still blocks merges on `lint` and `typecheck`. Fix the errors instead of bypassing checks.
5. Never commit secrets. Use `.env` and keep `.env.sample` current.
6. If you change image domains or fonts, document the change here.
7. Prefer small, reviewable PRs. Include screenshots for UI changes.
## 16) Common pitfalls
* Adding a remote image domain but forgetting to allow it in `next.config.ts`.
* Introducing a client component unnecessarily and breaking streaming or SSR.
* Duplicating navigation inside nested layouts.
* Styling drift by bypassing Tailwind utilities and shadcn primitives.
## 17) Quick command reference
```
# install deps
npm ci
# develop
npm run dev
# quality gates
npm run lint
npm run typecheck
# build and preview
npm run build
npm run start
# open-next build and deploy
npx open-next@latest build
npx wrangler deploy .open-next/worker
```
- **Lint & type-check** (lines 1435): `npm run lint` and `npm run type-check` run on every PR to main/develop. **Failures block merge.**
- **Build** (lines 3765): `npm run build` with `NODE_ENV=production` required before merge. Artifacts uploaded for 7 days.
Run both before pushing: `npm run lint && npm run type-check && npm run build`.
## Known Pitfalls
- **Build ignores errors**: next.config.ts (lines 1722) sets `eslint.ignoreDuringBuilds` and `typescript.ignoreBuildErrors` to `true`. Lint and typecheck **must pass locally** before deploy or CI will catch them. (Source: next.config.ts)
- **No test suite**: Manual QA only. Verify changes in Chromium and Safari before PR. (Source: README.md:55, AGENTS.md (this file, earlier))
- **Secrets in wrangler.toml**: Never commit credentials there. Use Gitea Secrets for env vars. (Source: CONTRIBUTING.md:153, AGENTS.md (this file, earlier))
- **Cloudflare compatibility**: Requires `nodejs_compat` flag and compatibility date ≥ 2024-09-23. (Source: wrangler.toml:4)

333
CLAUDE.md
View File

@ -1,168 +1,211 @@
# CLAUDE.md
# Agents Guide (Single Source of Truth)
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This document is the canonical reference for humans and `Agents` working in this repository. It standardizes stack, structure, workflows, and guardrails so contributions stay consistent and safe.
## Project Overview
## 1) Scope and goals
Biohazard VFX is a modern Next.js 15 website for a visual effects studio, deployed to Cloudflare Workers using OpenNext. The site showcases portfolio work, services, and provides a multi-step contact form for client intake.
* Make onboarding fast with a single place to look
* Define conventions so changes are predictable
* Provide exact commands that `Agents` can run without guesswork
* Prevent accidental regressions in routing, theming, SEO, and deployment
**Tech Stack:**
- Next.js 15.5.4 with App Router
- TypeScript (strict mode)
- React 19.1.0
- Tailwind CSS 4
- shadcn/ui (New York style)
- Framer Motion for animations
- Cloudflare Workers deployment via OpenNext
## 2) Tech stack
## Development Commands
* **Framework**: Next.js 15.5.4, React 19, TypeScript
* **Styling**: Tailwind CSS 4, shadcn/ui
* **Animation**: Framer Motion
* **Forms**: react-hook-form + Zod
* **Platform**: Cloudflare Workers via OpenNext
* **Package manager**: npm
* **Node**: LTS 20 or 22
```bash
# Development
npm run dev # Start dev server with Turbopack
npm run type-check # Run TypeScript compiler (no emit)
npm run lint # Run ESLint
## 3) Project layout
# Building
npm run build # Standard Next.js build
npm run build:open-next # Build for Cloudflare Workers (runs next build + open-next build)
# Production
npm start # Start Next.js production server (not used for Cloudflare deployment)
```
root
├─ src/
│ ├─ app/ # App Router pages and layouts
│ │ ├─ (marketing)/ # Example route groups
│ │ ├─ api/ # Route handlers
│ │ └─ layout.tsx # Root layout, see rules below
│ ├─ components/ # Reusable UI
│ ├─ data/ # JSON or TS data objects consumed by pages
│ ├─ lib/ # Utilities, hooks, server actions
│ ├─ styles/ # globals.css, tailwind utilities if applicable
│ └─ types/ # Shared types
├─ public/ # Static assets
├─ next.config.ts
├─ tailwind.config.ts
├─ wrangler.toml # Cloudflare deploy config
└─ package.json
```
## Deployment (Cloudflare Workers)
### Import aliases
The site deploys to Cloudflare Workers, not Vercel. Critical deployment files:
* Prefer absolute imports using `@` mapped to `src` via `tsconfig.json` paths.
- **`wrangler.toml`**: Cloudflare Workers configuration with routes for biohazardvfx.com
- **`open-next.config.ts`**: OpenNext adapter configuration for Cloudflare
- **`next.config.ts`**: Ignores lint/TypeScript errors during build (required for deployment)
## 4) Authoritative UI system
**Deploy commands:**
```bash
npx opennextjs-cloudflare build # Build for Cloudflare
npx wrangler deploy # Deploy to Cloudflare Workers
* **Theme**: dark mode is the default. Do not introduce light-first designs without approval.
* **Typography**: default to Geist and Geist Mono via CSS variables. If adding a new font, add it as a variable and document it here before use.
* **Components**: use shadcn/ui primitives. Extend with local wrappers placed in `src/components/ui/`.
* **Spacing and rhythm**: follow Tailwind 4 defaults. Prefer utility classes over custom CSS unless componentized.
* **Animation**: keep motion subtle. Framer Motion only for meaningful transitions.
## 5) Routing and layout rules
* The **root layout** owns global providers, theme class, `<Nav />`, and `<Footer />`. Do not duplicate these in child layouts.
* Pages live in `src/app`. Keep server components as the default. Promote to client component only when needed.
* Metadata must be defined per route with the Next.js Metadata API.
## 6) SEO and metadata
* Use the Metadata API for title, description, Open Graph, and Twitter cards.
* Add structured data with JSON-LD in the root layout or specific routes when required.
* All pages must render a unique `title` and `description` suitable for indexing.
## 7) Forms and validation
* Use `react-hook-form` with Zod schemas.
* Surface field-level errors and a generic submit error. Never swallow validation errors.
## 8) Images and assets
* Use Next Image component for remote images.
* If a new external domain is introduced, add it to `next.config.ts` remote patterns and document it here.
* Keep `public/` for truly static assets only.
## 9) Environment variables
Provide a `.env.sample` and keep it in sync. Typical keys:
```
NEXT_PUBLIC_SITE_URL=
RESEND_API_KEY=
CF_PAGES_URL=
```
**Live URLs:**
- Production: https://biohazardvfx.com
- Worker: https://biohazard-vfx-website.nicholaivogelfilms.workers.dev
Do not commit real secrets. `Agents` must fail a task rather than hardcode a secret.
**Cloudflare-specific requirements:**
- Requires `nodejs_compat` compatibility flag
- Compatibility date: `2024-09-23` or later
- Assets binding configured at `.open-next/assets`
## 10) Local development
## Architecture
```
# install
npm ci
### Path Aliases
- `@/*` maps to `./src/*` (configured in tsconfig.json)
- shadcn/ui aliases: `@/components`, `@/components/ui`, `@/lib/utils`
# run dev server
npm run dev
### App Structure (Next.js App Router)
# type checks and linting
npm run typecheck
npm run lint
Pages are in `src/app/`:
- `page.tsx` - Homepage with hero, featured projects, capabilities
- `about/page.tsx` - Studio origins, values, testimonials
- `services/page.tsx` - Service offerings with ServiceCard components
- `portfolio/page.tsx` - Masonry grid portfolio layout
- `contact/page.tsx` - Multi-step contact form
- `layout.tsx` - Root layout with Navigation and Footer (removed from individual pages)
**Important:** Navigation and Footer components are only in the root layout. Individual pages should NOT include them.
### Data Files
Content is separated from components in `src/data/`:
- `projects.ts` - Project data with Project interface (id, title, description, category, thumbnailUrl, videoUrl, aspectRatio, featured, tags)
- `services.ts` - Services data
When adding new projects or services, update these files rather than hardcoding data in components.
### Component Organization
Custom components in `src/components/`:
- **Layout:** Navigation, Footer
- **Page sections:** Hero, ContactSection, MissionSection, BrandingSection
- **Content display:** ProjectCard, ServiceCard, MasonryGrid, ProjectShowcase, VideoPlayer, VideoPreview
- **Interactive:** MultiStepForm (4-step client intake), HorizontalAccordion
- **Visual effects:** DepthMap, CursorDotBackground, ScrollProgressBar, SectionDivider
- **Third-party:** InstagramFeed, ClientLogoGrid
shadcn/ui components in `src/components/ui/`:
- Uses New York style variant
- Includes: accordion, button, card, dialog, hover-card, input, label, navigation-menu, progress, select, separator, textarea
### Styling System
**Fonts:** Nine Google Fonts preloaded in layout.tsx:
- Geist Sans (primary), Geist Mono
- Bebas Neue, Orbitron, Inter, JetBrains Mono, Space Mono, Rajdhani, Exo 2
- Available as CSS variables: `--font-geist-sans`, `--font-bebas`, etc.
**Theme:**
- Dark mode by default (`className="dark"` on html element)
- CSS variables in `src/app/globals.css`
- shadcn/ui config in `components.json` (New York style, neutral base color)
**Tailwind:**
- Tailwind CSS 4 with PostCSS
- Config: `tailwind.config.ts`
- Global styles: `src/app/globals.css`
### SEO & Metadata
All pages include Next.js Metadata API:
- Open Graph tags
- Twitter cards
- Canonical links
- JSON-LD structured data (Organization schema in root layout)
- metadataBase: `https://biohazardvfx.com`
### Forms & Validation
MultiStepForm component uses:
- react-hook-form for form state
- zod for validation (via @hookform/resolvers)
- 4 steps: Project Type → Budget/Timeline → Project Details → Contact Info
- Progress indicator using shadcn/ui Progress component
### Images
Next.js Image optimization configured in next.config.ts:
- Remote patterns allowed: `images.unsplash.com`
- `unoptimized: false` (optimization enabled)
## Key Constraints
1. **Cloudflare deployment:** Do not suggest Vercel-specific features that won't work with OpenNext
2. **Build config:** Lint and TypeScript errors are ignored during build (required for deployment). Do not remove these settings from next.config.ts
3. **Dark theme only:** Site uses dark mode by default. Do not create light mode variants unless explicitly requested
4. **Layout structure:** Navigation and Footer are in root layout only. Do not add them to individual pages
5. **Data separation:** Project and service data lives in `src/data/`. Keep content separate from components
## Common Tasks
**Add a new page:**
1. Create directory in `src/app/`
2. Add `page.tsx` with metadata export
3. Update Navigation component at `src/components/Navigation.tsx`
**Add a new shadcn/ui component:**
```bash
npx shadcn@latest add [component-name]
# build and preview
npm run build
npm run start
```
**Update project portfolio:**
1. Edit `src/data/projects.ts`
2. Add new Project object to projects array
3. Ensure aspectRatio is set correctly for masonry layout
Notes
**Update services:**
1. Edit `src/data/services.ts`
2. Add service object with required fields
* The Next build may be configured to ignore ESLint and TS errors for production bundling speed. CI still gates on `lint` and `typecheck` before merge.
## 11) Deployment on Cloudflare Workers with OpenNext
### Required wrangler.toml settings
```
name = "site-worker"
main = ".open-next/worker/index.mjs"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
assets = { directory = ".open-next/assets" }
```
### Build and deploy
```
# produce OpenNext build artifacts
npx open-next@latest build
# deploy worker and assets
npx wrangler deploy .open-next/worker
```
Guidelines
* Always run `npm run typecheck` and `npm run lint` before build.
* Ensure `assets.directory` matches the OpenNext output.
* Keep the compatibility date at or after 2024-09-23.
## 12) Branching, commits, and CI
* **Default branch**: `main` is protected.
* **Workflow**: feature branches -> PR -> required checks -> squash merge.
* **Commit format**: Conventional Commits. Examples
* `feat: add contact form schema`
* `fix: correct Image remote pattern`
* `chore: bump dependencies`
* **Required checks**
* `npm run lint`
* `npm run typecheck`
* `npm run build` can be optional locally if CI runs it, but must succeed before deploy.
## 13) Testing
* **Unit**: place tests close to sources, name with `.test.ts` or `.test.tsx`.
* **E2E**: optional Playwright. If used, add a `playwright.config.ts` and a `npm run e2e` script.
## 14) Data and content
* Non-secret content belongs in `src/data` as TS modules or JSON. Keep it presentation-agnostic.
* Do not fetch static project data in client components. Prefer server components or file imports.
## 15) `Agents` operating rules
1. Read this guide before making changes.
2. Do not alter the root layout structure for global nav or footer. Extend only via component props or slots.
3. Before adding a dependency, justify it in the PR description and update this document if it affects conventions.
4. When creating pages, set Metadata and verify unique title and description.
5. If a build ignores ESLint or TS errors, CI still blocks merges on `lint` and `typecheck`. Fix the errors instead of bypassing checks.
6. Never commit secrets. Use `.env` and keep `.env.sample` current.
7. If you change image domains or fonts, document the change here.
8. Prefer small, reviewable PRs. Include screenshots for UI changes.
9. **When adding files to `public/`**, always update the middleware whitelist in `src/middleware.ts` (line 8) to allow access to the new files.
## 16) Common pitfalls
* Adding a remote image domain but forgetting to allow it in `next.config.ts`.
* Introducing a client component unnecessarily and breaking streaming or SSR.
* Duplicating navigation inside nested layouts.
* Styling drift by bypassing Tailwind utilities and shadcn primitives.
* **⚠️ CRITICAL - Middleware Whitelist**: `src/middleware.ts` redirects ALL routes to `/` except explicitly whitelisted paths. When adding new static assets to `public/` (images, videos, PDFs, etc.), you MUST add the path to the middleware allowlist (line 8) or the file will return a 307 redirect to `/` instead of serving. Common symptom: video/image returns "text/html" Content-Type error.
## 17) Quick command reference
```
# install deps
npm ci
# develop
npm run dev
# quality gates
npm run lint
npm run typecheck
# build and preview
npm run build
npm run start
# open-next build and deploy
npx open-next@latest build
npx wrangler deploy .open-next/worker
```
## 18) Change management
* Any modification to guardrails in sections 4 to 12 requires a PR that updates this document.
* Keep this file the single place that defines expectations for humans and `Agents`.
**Modify theme colors:**
1. Edit CSS variables in `src/app/globals.css`
2. Theme uses neutral base color with CSS variables for theming

205
QWEN.md Normal file
View File

@ -0,0 +1,205 @@
# Agents Guide (Single Source of Truth)
This document is the canonical reference for humans and `Agents` working in this repository. It standardizes stack, structure, workflows, and guardrails so contributions stay consistent and safe.
## 1) Scope and goals
* Make onboarding fast with a single place to look
* Define conventions so changes are predictable
* Provide exact commands that `Agents` can run without guesswork
* Prevent accidental regressions in routing, theming, SEO, and deployment
## 2) Tech stack
* **Framework**: Next.js 15.5.4, React 19, TypeScript
* **Styling**: Tailwind CSS 4, shadcn/ui
* **Animation**: Framer Motion
* **Forms**: react-hook-form + Zod
* **Platform**: Cloudflare Workers via OpenNext
* **Package manager**: npm
* **Node**: LTS 20 or 22
## 3) Project layout
```
root
├─ src/
│ ├─ app/ # App Router pages and layouts
│ │ ├─ (marketing)/ # Example route groups
│ │ ├─ api/ # Route handlers
│ │ └─ layout.tsx # Root layout, see rules below
│ ├─ components/ # Reusable UI
│ ├─ data/ # JSON or TS data objects consumed by pages
│ ├─ lib/ # Utilities, hooks, server actions
│ ├─ styles/ # globals.css, tailwind utilities if applicable
│ └─ types/ # Shared types
├─ public/ # Static assets
├─ next.config.ts
├─ tailwind.config.ts
├─ wrangler.toml # Cloudflare deploy config
└─ package.json
```
### Import aliases
* Prefer absolute imports using `@` mapped to `src` via `tsconfig.json` paths.
## 4) Authoritative UI system
* **Theme**: dark mode is the default. Do not introduce light-first designs without approval.
* **Typography**: default to Geist and Geist Mono via CSS variables. If adding a new font, add it as a variable and document it here before use.
* **Components**: use shadcn/ui primitives. Extend with local wrappers placed in `src/components/ui/`.
* **Spacing and rhythm**: follow Tailwind 4 defaults. Prefer utility classes over custom CSS unless componentized.
* **Animation**: keep motion subtle. Framer Motion only for meaningful transitions.
## 5) Routing and layout rules
* The **root layout** owns global providers, theme class, `<Nav />`, and `<Footer />`. Do not duplicate these in child layouts.
* Pages live in `src/app`. Keep server components as the default. Promote to client component only when needed.
* Metadata must be defined per route with the Next.js Metadata API.
## 6) SEO and metadata
* Use the Metadata API for title, description, Open Graph, and Twitter cards.
* Add structured data with JSON-LD in the root layout or specific routes when required.
* All pages must render a unique `title` and `description` suitable for indexing.
## 7) Forms and validation
* Use `react-hook-form` with Zod schemas.
* Surface field-level errors and a generic submit error. Never swallow validation errors.
## 8) Images and assets
* Use Next Image component for remote images.
* If a new external domain is introduced, add it to `next.config.ts` remote patterns and document it here.
* Keep `public/` for truly static assets only.
## 9) Environment variables
Provide a `.env.sample` and keep it in sync. Typical keys:
```
NEXT_PUBLIC_SITE_URL=
RESEND_API_KEY=
CF_PAGES_URL=
```
Do not commit real secrets. `Agents` must fail a task rather than hardcode a secret.
## 10) Local development
```
# install
npm ci
# run dev server
npm run dev
# type checks and linting
npm run typecheck
npm run lint
# build and preview
npm run build
npm run start
```
Notes
* The Next build may be configured to ignore ESLint and TS errors for production bundling speed. CI still gates on `lint` and `typecheck` before merge.
## 11) Deployment on Cloudflare Workers with OpenNext
### Required wrangler.toml settings
```
name = "site-worker"
main = ".open-next/worker/index.mjs"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
assets = { directory = ".open-next/assets" }
```
### Build and deploy
```
# produce OpenNext build artifacts
npx open-next@latest build
# deploy worker and assets
npx wrangler deploy .open-next/worker
```
Guidelines
* Always run `npm run typecheck` and `npm run lint` before build.
* Ensure `assets.directory` matches the OpenNext output.
* Keep the compatibility date at or after 2024-09-23.
## 12) Branching, commits, and CI
* **Default branch**: `main` is protected.
* **Workflow**: feature branches -> PR -> required checks -> squash merge.
* **Commit format**: Conventional Commits. Examples
* `feat: add contact form schema`
* `fix: correct Image remote pattern`
* `chore: bump dependencies`
* **Required checks**
* `npm run lint`
* `npm run typecheck`
* `npm run build` can be optional locally if CI runs it, but must succeed before deploy.
## 13) Testing
* **Unit**: place tests close to sources, name with `.test.ts` or `.test.tsx`.
* **E2E**: optional Playwright. If used, add a `playwright.config.ts` and a `npm run e2e` script.
## 14) Data and content
* Non-secret content belongs in `src/data` as TS modules or JSON. Keep it presentation-agnostic.
* Do not fetch static project data in client components. Prefer server components or file imports.
## 15) `Agents` operating rules
1. Read this guide before making changes.
2. Before adding a dependency, justify it in the PR description and update this document if it affects conventions.
3. When creating pages, set Metadata and verify unique title and description.
4. If a build ignores ESLint or TS errors, CI still blocks merges on `lint` and `typecheck`. Fix the errors instead of bypassing checks.
5. Never commit secrets. Use `.env` and keep `.env.sample` current.
6. If you change image domains or fonts, document the change here.
7. Prefer small, reviewable PRs. Include screenshots for UI changes.
## 16) Common pitfalls
* Adding a remote image domain but forgetting to allow it in `next.config.ts`.
* Introducing a client component unnecessarily and breaking streaming or SSR.
* Duplicating navigation inside nested layouts.
* Styling drift by bypassing Tailwind utilities and shadcn primitives.
## 17) Quick command reference
```
# install deps
npm ci
# develop
npm run dev
# quality gates
npm run lint
npm run typecheck
# build and preview
npm run build
npm run start
# open-next build and deploy
npx open-next@latest build
npx wrangler deploy .open-next/worker
```

View File

@ -20,6 +20,20 @@ const nextConfig: NextConfig = {
typescript: {
ignoreBuildErrors: true,
},
// Custom headers for video files
async headers() {
return [
{
source: "/:path*.mp4",
headers: [
{
key: "Content-Type",
value: "video/mp4",
},
],
},
];
},
};
export default nextConfig;

BIN
public/reel.mp4 Normal file

Binary file not shown.

View File

@ -180,6 +180,7 @@ body {
html {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
scroll-behavior: smooth;
}
html::-webkit-scrollbar {

View File

@ -0,0 +1,258 @@
"use client";
import { useRef, useState, useEffect } from "react";
import { Play, Pause, Volume2, VolumeX, Maximize, AlertCircle } from "lucide-react";
interface ReelPlayerProps {
src: string;
className?: string;
}
export function ReelPlayer({ src, className = "" }: ReelPlayerProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const progressBarRef = useRef<HTMLDivElement>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [isMuted, setIsMuted] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(0);
const [volume, setVolume] = useState(1);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const video = videoRef.current;
if (!video) return;
const handleLoadedMetadata = () => {
setDuration(video.duration);
};
const handleTimeUpdate = () => {
setCurrentTime(video.currentTime);
};
const handleEnded = () => {
setIsPlaying(false);
};
const handleError = (e: Event) => {
setIsLoading(false);
const videoEl = e.target as HTMLVideoElement;
const errorCode = videoEl.error?.code;
const errorMessage = videoEl.error?.message;
let userMessage = "Failed to load video. ";
switch (errorCode) {
case 1:
userMessage += "Video loading was aborted.";
break;
case 2:
userMessage += "Network error occurred.";
break;
case 3:
userMessage += "Video format not supported by your browser.";
break;
case 4:
userMessage += "Video source not found.";
break;
default:
userMessage += errorMessage || "Unknown error.";
}
setError(userMessage);
console.error("Video error:", errorCode, errorMessage);
};
const handleCanPlay = () => {
console.log("Video canplay event fired");
setIsLoading(false);
setError(null);
};
const handleLoadedData = () => {
console.log("Video loadeddata event fired");
setIsLoading(false);
};
video.addEventListener("loadedmetadata", handleLoadedMetadata);
video.addEventListener("timeupdate", handleTimeUpdate);
video.addEventListener("ended", handleEnded);
video.addEventListener("error", handleError);
video.addEventListener("canplay", handleCanPlay);
video.addEventListener("loadeddata", handleLoadedData);
// Check if video is already loaded (in case events fired before listeners attached)
if (video.readyState >= 3) {
// HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA
console.log("Video already loaded, readyState:", video.readyState);
setIsLoading(false);
if (video.duration) {
setDuration(video.duration);
}
}
return () => {
video.removeEventListener("loadedmetadata", handleLoadedMetadata);
video.removeEventListener("timeupdate", handleTimeUpdate);
video.removeEventListener("ended", handleEnded);
video.removeEventListener("error", handleError);
video.removeEventListener("canplay", handleCanPlay);
video.removeEventListener("loadeddata", handleLoadedData);
};
}, []);
const togglePlay = async () => {
const video = videoRef.current;
if (!video || error) return;
try {
if (isPlaying) {
video.pause();
setIsPlaying(false);
} else {
await video.play();
setIsPlaying(true);
}
} catch (err) {
console.error("Play error:", err);
setError("Unable to play video. " + (err as Error).message);
setIsPlaying(false);
}
};
const toggleMute = () => {
const video = videoRef.current;
if (!video) return;
video.muted = !isMuted;
setIsMuted(!isMuted);
};
const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => {
const video = videoRef.current;
const progressBar = progressBarRef.current;
if (!video || !progressBar) return;
const rect = progressBar.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const percentage = clickX / rect.width;
video.currentTime = percentage * video.duration;
};
const toggleFullscreen = () => {
const video = videoRef.current;
if (!video) return;
if (!document.fullscreenElement) {
video.requestFullscreen();
} else {
document.exitFullscreen();
}
};
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, "0")}`;
};
const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
return (
<div className={`relative bg-black border border-white/10 ${className}`}>
{/* Video Element */}
<video
ref={videoRef}
className="w-full aspect-video bg-black"
onClick={togglePlay}
preload="auto"
playsInline
>
<source src={src} type="video/mp4" />
Your browser does not support the video tag.
</video>
{/* Loading State */}
{isLoading && !error && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<div className="text-white text-sm">Loading video...</div>
</div>
)}
{/* Error State */}
{error && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black/80 p-4">
<AlertCircle className="w-12 h-12 text-[#ff4d00] mb-3" />
<div className="text-white text-sm text-center max-w-md">
{error}
</div>
<div className="text-gray-400 text-xs mt-2">
Try refreshing the page or using a different browser.
</div>
</div>
)}
{/* Custom Controls */}
{!error && (
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 via-black/70 to-transparent p-4">
{/* Progress Bar */}
<div
ref={progressBarRef}
className="w-full h-1 bg-white/20 cursor-pointer mb-3 relative"
onClick={handleProgressClick}
>
<div
className="h-full bg-[#ff4d00] transition-all duration-100"
style={{ width: `${progress}%` }}
/>
</div>
{/* Controls Row */}
<div className="flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
{/* Play/Pause Button */}
<button
onClick={togglePlay}
className="text-white hover:text-[#ff4d00] transition-colors"
aria-label={isPlaying ? "Pause" : "Play"}
>
{isPlaying ? (
<Pause className="w-5 h-5" />
) : (
<Play className="w-5 h-5" />
)}
</button>
{/* Volume Button */}
<button
onClick={toggleMute}
className="text-white hover:text-[#ff4d00] transition-colors"
aria-label={isMuted ? "Unmute" : "Mute"}
>
{isMuted ? (
<VolumeX className="w-5 h-5" />
) : (
<Volume2 className="w-5 h-5" />
)}
</button>
{/* Time Display */}
<span className="text-white text-sm font-mono">
{formatTime(currentTime)} / {formatTime(duration)}
</span>
</div>
{/* Fullscreen Button */}
<button
onClick={toggleFullscreen}
className="text-white hover:text-[#ff4d00] transition-colors"
aria-label="Fullscreen"
>
<Maximize className="w-5 h-5" />
</button>
</div>
</div>
)}
</div>
);
}

View File

@ -1,11 +1,11 @@
"use client";
import { CursorDotBackground } from "./CursorDotBackground";
import { HorizontalAccordion } from "./HorizontalAccordion";
import { InstagramFeed } from "./InstagramFeed";
import { ScrollProgressBar } from "./ScrollProgressBar";
import { SectionDivider } from "./SectionDivider";
import { VideoPreview } from "./VideoPreview";
import { ReelPlayer } from "./ReelPlayer";
import { useEffect, useRef, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { DepthMap } from "./DepthMap";
@ -95,67 +95,73 @@ export function TempPlaceholder() {
<>
<ScrollProgressBar />
<section className="py-8 md:py-16 bg-black text-white min-h-screen">
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-3xl">
<div className="relative">
<div className="relative rounded-[4px] bg-gradient-to-br from-[#1a0f24]/35 via-[#ff4d00]/30 to-[#1a0f24]/35 p-[1px]">
<div className="relative rounded-[3px] bg-black/92">
<div className="relative p-4 sm:p-6 md:p-8">
<CursorDotBackground
dotSize={2}
dotSpacing={20}
fadeDistance={80}
opacity={0.25}
className="rounded"
/>
<motion.div
className="relative z-10"
variants={containerVariants}
initial="hidden"
animate="visible"
transition={{
staggerChildren: 0.1,
delayChildren: 0.1,
}}
>
<motion.p
className="text-sm text-gray-500 mb-6"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
Last updated: 10-12-2025
</motion.p>
<div className="container mx-auto px-4 sm:px-6 lg:px-8 max-w-[900px]">
{/* Navigation */}
<nav className="mb-12 md:mb-16">
<div className="flex justify-between items-center py-6 border-b border-white/10">
<div className="text-lg font-mono tracking-tight">BIOHAZARD</div>
<div className="flex gap-6 text-sm">
<a href="#about" className="hover:text-[#ff4d00] transition-colors">About</a>
<a href="#work" className="hover:text-[#ff4d00] transition-colors">Work</a>
<a href="#studio" className="hover:text-[#ff4d00] transition-colors">Studio</a>
<a href="#contact" className="hover:text-[#ff4d00] transition-colors">Contact</a>
</div>
</div>
</nav>
<motion.h1
ref={titleRef}
className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 leading-tight"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<span ref={titleInnerRef} className="inline-block">
You've gotta be <em className="text-gray-400">shittin'</em> me.
</span>
</motion.h1>
<motion.p
className="text-base sm:text-lg mb-2 text-gray-300"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
This is the 20th time this has happened.
</motion.p>
<motion.p
className="text-base sm:text-lg mb-6 md:mb-8 text-gray-400"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<em>Nicholai broke the website, again.</em>
</motion.p>
{/* Main Card Container */}
<div className="relative bg-[#0a0a0a] border border-white/5 p-6 sm:p-8 md:p-12">
<motion.div
className="relative"
variants={containerVariants}
initial="hidden"
animate="visible"
transition={{
staggerChildren: 0.1,
delayChildren: 0.1,
}}
>
{/* About Section */}
<section id="about" className="mb-16 md:mb-20">
<motion.p
className="text-sm text-gray-500 mb-6"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
Last updated: 10-12-2025
</motion.p>
<motion.div
className="mb-8"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<HorizontalAccordion trigger="How did we get here?">
<motion.h1
ref={titleRef}
className="text-3xl sm:text-4xl md:text-5xl font-bold mb-4 leading-tight"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<span ref={titleInnerRef} className="inline-block">
You've gotta be <em className="text-gray-400">shittin'</em> me.
</span>
</motion.h1>
<motion.p
className="text-base sm:text-lg mb-2 text-gray-300"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
This is the 20th time this has happened.
</motion.p>
<motion.p
className="text-base sm:text-lg mb-6 md:mb-8 text-gray-400"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<em>Nicholai broke the website, again.</em>
</motion.p>
<motion.div
className="mb-8"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<HorizontalAccordion trigger="How did we get here?">
<div className="w-full">
<p className="mb-4 text-gray-400 text-sm">
<em>(TLDR: perfectionism is the mind killer)</em>
@ -236,7 +242,7 @@ export function TempPlaceholder() {
className="z-50 w-[90vw] max-w-[350px]"
onMouseLeave={() => setIsEasterEggOpen(false)}
>
<div className="relative bg-black rounded-lg overflow-hidden shadow-2xl">
<div className="relative bg-black overflow-hidden shadow-2xl">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
@ -287,7 +293,7 @@ export function TempPlaceholder() {
className="z-50 w-[90vw] max-w-[400px]"
onMouseLeave={() => setIsPigeonEggOpen(false)}
>
<div className="relative bg-black rounded-lg overflow-hidden shadow-2xl">
<div className="relative bg-black overflow-hidden shadow-2xl">
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
@ -312,7 +318,7 @@ export function TempPlaceholder() {
</>
)}
</AnimatePresence>
<motion.p
<motion.p
className="mb-6 md:mb-8 text-base sm:text-lg text-gray-300"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
@ -320,48 +326,36 @@ export function TempPlaceholder() {
<strong>Who we are:</strong> artists and technical people, we're
better at VFX than we are at web design, I promise.
</motion.p>
</section>
<SectionDivider />
<motion.p
className="mb-4 text-base sm:text-lg"
{/* Work Section */}
<section id="work" className="mb-16 md:mb-20">
<motion.p
className="mb-6 text-base sm:text-lg"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<strong>Here's our reel:</strong>{" "}
<motion.a
href="https://f.io/Wgx3EAHu"
className="inline-block break-words relative"
style={{ color: '#ff4d00' }}
target="_blank"
rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
>
<span className="relative inline-block">
Biohazard Reel Mar 2025 - Frame.io
<motion.span
className="absolute bottom-0 left-0 h-[1px] bg-current"
initial={{ scaleX: 0 }}
whileHover={{ scaleX: 1 }}
transition={{ duration: 0.3, ease: "easeOut" }}
style={{ transformOrigin: 'left', width: '100%' }}
/>
</span>
</motion.a>
<strong>&gt; Here's our reel:</strong>
</motion.p>
<motion.div
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
className="mb-8"
>
<ReelPlayer src="/reel.mp4" />
</motion.div>
<SectionDivider />
<motion.p
<motion.p
className="mb-4 md:mb-6 text-base sm:text-lg"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<strong>Some projects we've worked on:</strong>
<strong>&gt; Some projects we've worked on:</strong>
</motion.p>
<motion.ul
@ -381,9 +375,7 @@ export function TempPlaceholder() {
style={{ color: '#ff4d00' }}
target="_blank"
rel="noopener noreferrer"
whileHover={{
textShadow: '0 0 8px rgba(255, 77, 0, 0.6)',
}}
whileHover={{ opacity: 0.8 }}
whileTap={{ scale: 0.98 }}
transition={{ duration: 0.2 }}
>
@ -604,28 +596,34 @@ export function TempPlaceholder() {
</HoverCard>
</motion.li>
</motion.ul>
</section>
<SectionDivider />
<motion.div
{/* Studio Section */}
<section id="studio" className="mb-16 md:mb-20">
<motion.div
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<InstagramFeed />
</motion.div>
</section>
<SectionDivider />
<motion.div
{/* Contact Section */}
<section id="contact">
<motion.div
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
<motion.p
<motion.p
className="mb-4 text-sm sm:text-base text-gray-300"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
Contact us:{" "}
<strong>&gt; Contact us:</strong>{" "}
<motion.a
href="mailto:contact@biohazardvfx.com"
className="break-words inline-block relative"
@ -650,12 +648,12 @@ export function TempPlaceholder() {
</span>
</motion.a>
</motion.p>
<motion.p
className="text-sm sm:text-base text-gray-300 pb-6 border-b border-gray-800"
<motion.p
className="text-sm sm:text-base text-gray-300 pb-6 border-b border-white/10"
variants={itemVariants}
transition={{ duration: 0.4, ease: "easeOut" }}
>
File a complaint:{" "}
<strong>&gt; File a complaint:</strong>{" "}
<motion.a
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
className="break-words inline-block relative"
@ -692,13 +690,11 @@ export function TempPlaceholder() {
viewport={{ once: true, margin: "-50px" }}
transition={{ duration: 0.6, ease: "easeOut", delay: 0.2 }}
>
No pigeons allowed in this zone
No pigeons allowed in this zone
</motion.p>
</motion.div>
</motion.div>
</div>
</div>
</div>
</section>
</motion.div>
</div>
</div>
</section>

View File

@ -5,7 +5,7 @@ export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Allow only the home page and Next.js internal routes
if (pathname === '/' || pathname.startsWith('/_next') || pathname.startsWith('/favicon.') || pathname === '/OLIVER.jpeg' || pathname === '/OLIVER_depth.jpeg' || pathname === '/no_pigeons_zone.gif') {
if (pathname === '/' || pathname.startsWith('/_next') || pathname.startsWith('/favicon.') || pathname === '/OLIVER.jpeg' || pathname === '/OLIVER_depth.jpeg' || pathname === '/no_pigeons_zone.gif' || pathname === '/reel.mp4') {
return NextResponse.next();
}