From dab8b4e93c0637a61017ffd26a31f9a72aaa6985 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 00:51:45 -0700
Subject: [PATCH 01/18] Add TypeScript + Vite build setup
Refactor inline CSS/JS into src/styles and src/scripts TypeScript modules, add Vite multi-page build config, update GitHub Pages workflow to build and deploy from dist/, sync front-end to preview.html and contact.html source files.
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
.github/workflows/pages.yml | 14 +-
.gitignore | 5 +-
package-lock.json | 1137 +++++++++++++++++++++++++++++++++++
package.json | 17 +
src/scripts/contact.ts | 1 +
src/scripts/index.ts | 50 ++
src/styles/contact.css | 37 ++
src/styles/index.css | 988 ++++++++++++++++++++++++++++++
tsconfig.json | 11 +
vite.config.ts | 16 +
10 files changed, 2272 insertions(+), 4 deletions(-)
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 src/scripts/contact.ts
create mode 100644 src/scripts/index.ts
create mode 100644 src/styles/contact.css
create mode 100644 src/styles/index.css
create mode 100644 tsconfig.json
create mode 100644 vite.config.ts
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 2577e37..53febc0 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -29,10 +29,22 @@ jobs:
with:
enablement: true
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build
+ run: npm run build
+
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
- path: .
+ path: ./dist
deploy:
needs: build
diff --git a/.gitignore b/.gitignore
index 3dc844d..da669ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
-public/
-resources/
-.hugo_build.lock
+dist/
+node_modules/
# OS / editor
.DS_Store
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..92e5098
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1137 @@
+{
+ "name": "testopsengineering-site",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "testopsengineering-site",
+ "version": "1.0.0",
+ "devDependencies": {
+ "@types/node": "^24.3.0",
+ "typescript": "^5.9.2",
+ "vite": "^7.1.3"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.13.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
+ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.20",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz",
+ "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.16",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
+ "@rollup/rollup-android-arm64": "4.62.2",
+ "@rollup/rollup-darwin-arm64": "4.62.2",
+ "@rollup/rollup-darwin-x64": "4.62.2",
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
+ "@rollup/rollup-freebsd-x64": "4.62.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
+ "@rollup/rollup-openbsd-x64": "4.62.2",
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
+ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..52003fb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "testopsengineering-site",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "typecheck": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@types/node": "^24.3.0",
+ "typescript": "^5.9.2",
+ "vite": "^7.1.3"
+ }
+}
diff --git a/src/scripts/contact.ts b/src/scripts/contact.ts
new file mode 100644
index 0000000..fc36296
--- /dev/null
+++ b/src/scripts/contact.ts
@@ -0,0 +1 @@
+import "../styles/contact.css";
diff --git a/src/scripts/index.ts b/src/scripts/index.ts
new file mode 100644
index 0000000..9498ec3
--- /dev/null
+++ b/src/scripts/index.ts
@@ -0,0 +1,50 @@
+import "../styles/index.css";
+
+declare global {
+ interface Window {
+ toggleMenu: () => void;
+ closeMenu: () => void;
+ }
+}
+
+const mobileMenu = document.getElementById("mobileMenu");
+const hamburger = document.querySelector(".nav-hamburger");
+
+window.toggleMenu = () => {
+ mobileMenu?.classList.toggle("open");
+};
+
+window.closeMenu = () => {
+ mobileMenu?.classList.remove("open");
+};
+
+document.addEventListener("click", (event) => {
+ if (!mobileMenu) return;
+ if (!hamburger) return;
+ const target = event.target as Node;
+ if (!mobileMenu.classList.contains("open")) return;
+ if (mobileMenu.contains(target)) return;
+ if (hamburger.contains(target)) return;
+ mobileMenu.classList.remove("open");
+});
+
+const sections = document.querySelectorAll("section[id], div[id]");
+const navLinks = Array.from(document.querySelectorAll(".nav-links a"))
+ .filter((link) => link.getAttribute("href")?.startsWith("#"));
+
+window.addEventListener("scroll", () => {
+ const scrollY = window.pageYOffset;
+ sections.forEach((section) => {
+ const id = section.getAttribute("id");
+ if (!id) return;
+ const top = section.offsetTop - 80;
+ const bottom = top + section.offsetHeight;
+ if (scrollY >= top) {
+ if (scrollY < bottom) {
+ navLinks.forEach((link) => {
+ link.style.color = link.getAttribute("href") === `#${id}` ? "var(--accent)" : "";
+ });
+ }
+ }
+ });
+});
diff --git a/src/styles/contact.css b/src/styles/contact.css
new file mode 100644
index 0000000..7f8bf89
--- /dev/null
+++ b/src/styles/contact.css
@@ -0,0 +1,37 @@
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+:root {
+ --bg:#07090c; --bg2:#0e1116; --bg3:#141820;
+ --border:#1c2230; --border2:#263040;
+ --accent:#00d4a8; --text:#dde4ee; --text2:#7a8fa8;
+ --mono:'DM Mono',monospace; --serif:'Instrument Serif',serif; --body:'DM Sans',sans-serif;
+}
+body { background: var(--bg); color: var(--text); font-family: var(--body); line-height: 1.7; }
+.wrap { max-width: 860px; margin: 0 auto; padding: 0 1.5rem; }
+header { border-bottom: 1px solid var(--border); }
+.top { display:flex; justify-content:space-between; align-items:center; padding: 1rem 0; }
+.brand { color: var(--text); text-decoration:none; font-family: var(--mono); font-size: 13px; letter-spacing: .03em; }
+.brand img { width: 16px; height: 16px; object-fit: contain; margin-right:8px; vertical-align:middle; }
+.back { color: var(--text2); text-decoration:none; font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }
+.hero { padding: 3rem 0 1.5rem; }
+.kicker { font-family: var(--mono); font-size: 10px; color: var(--accent); letter-spacing:.2em; text-transform: uppercase; margin-bottom: .9rem; }
+h1 { font-family: var(--serif); font-weight: 400; font-size: clamp(34px,5vw,54px); line-height: 1.1; margin-bottom: .8rem; }
+.sub { color: var(--text2); max-width: 620px; }
+.note { margin-top: 1rem; font-size: 12px; color: #f5a623; font-family: var(--mono); }
+form { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin: 2rem 0 3rem; }
+.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
+.row { margin-bottom: 1rem; }
+label { display:block; margin-bottom:.35rem; color: var(--text2); font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; }
+input, select, textarea {
+ width:100%; background: var(--bg3); border:1px solid var(--border); color: var(--text);
+ border-radius: 4px; padding: .72rem .85rem; font-family: var(--body); font-size: 14px;
+}
+input:focus, select:focus, textarea:focus { outline: none; border-color: var(--border2); }
+textarea { min-height: 140px; resize: vertical; }
+.btn {
+ display:inline-block; margin-top:.5rem; background: var(--accent); color: var(--bg);
+ border:0; padding: .8rem 1rem; border-radius: 3px; cursor:pointer;
+ font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
+}
+.btn:hover { opacity: .9; }
+footer { border-top: 1px solid var(--border); padding: 1rem 0 2rem; color: var(--text2); font-family: var(--mono); font-size: 11px; }
+@media (max-width: 720px) { .grid { grid-template-columns: 1fr; } }
diff --git a/src/styles/index.css b/src/styles/index.css
new file mode 100644
index 0000000..3362f07
--- /dev/null
+++ b/src/styles/index.css
@@ -0,0 +1,988 @@
+/* ============================================================
+ RESET & VARIABLES
+ ============================================================ */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+:root {
+ --bg: #07090c;
+ --bg2: #0e1116;
+ --bg3: #141820;
+ --bg4: #1a1f28;
+ --border: #1c2230;
+ --border2: #263040;
+ --border3: #2e3a4e;
+ --accent: #00d4a8;
+ --accent2: #4f8dff;
+ --accent3: #f5a623;
+ --text: #dde4ee;
+ --text2: #7a8fa8;
+ --text3: #3d4f63;
+ --text4: #1e2d3d;
+ --mono: 'DM Mono', monospace;
+ --serif: 'Instrument Serif', serif;
+ --body: 'DM Sans', sans-serif;
+}
+
+html { scroll-behavior: smooth; }
+
+body {
+ background: var(--bg);
+ color: var(--text);
+ font-family: var(--body);
+ font-size: 15px;
+ line-height: 1.7;
+ overflow-x: hidden;
+}
+
+/* ============================================================
+ LAYOUT
+ ============================================================ */
+.wrap { max-width: 1100px; margin: 0 auto; padding: 0 2.5rem; }
+
+/* ============================================================
+ NAV
+ ============================================================ */
+nav {
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ background: rgba(7, 9, 12, 0.94);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
+ border-bottom: 1px solid var(--border);
+}
+
+.nav-inner {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 0 2.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 58px;
+}
+
+.logo {
+ font-family: var(--mono);
+ font-size: 13px;
+ color: var(--text);
+ text-decoration: none;
+ letter-spacing: 0.03em;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.logo img { width: 34px; height: 34px; object-fit: contain; display: block; }
+
+.logo-dot {
+ width: 7px;
+ height: 7px;
+ background: var(--accent);
+ border-radius: 50%;
+ display: block;
+ flex-shrink: 0;
+}
+
+.nav-right { display: flex; align-items: center; gap: 2rem; }
+
+.nav-links { display: flex; gap: 2rem; list-style: none; }
+
+.nav-links a {
+ font-family: var(--mono);
+ font-size: 11px;
+ color: var(--text3);
+ text-decoration: none;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ transition: color 0.2s;
+}
+.nav-links a:hover { color: var(--text2); }
+
+.nav-cta {
+ font-family: var(--mono);
+ font-size: 11px;
+ background: var(--accent);
+ color: var(--bg);
+ padding: 8px 20px;
+ border-radius: 3px;
+ text-decoration: none;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ font-weight: 500;
+ transition: opacity 0.2s;
+ white-space: nowrap;
+}
+.nav-cta:hover { opacity: 0.85; }
+
+/* Hamburger */
+.nav-hamburger {
+ display: none;
+ flex-direction: column;
+ gap: 5px;
+ cursor: pointer;
+ padding: 4px;
+ background: none;
+ border: none;
+}
+.nav-hamburger span {
+ display: block;
+ width: 20px;
+ height: 1.5px;
+ background: var(--text2);
+ transition: all 0.2s;
+}
+
+/* Mobile menu */
+.mobile-menu {
+ display: none;
+ position: fixed;
+ top: 58px;
+ left: 0;
+ right: 0;
+ background: var(--bg2);
+ border-bottom: 1px solid var(--border);
+ padding: 1.5rem 2.5rem;
+ z-index: 99;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+.mobile-menu.open { display: flex; }
+
+.mobile-menu a {
+ font-family: var(--mono);
+ font-size: 13px;
+ color: var(--text2);
+ text-decoration: none;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ transition: color 0.2s;
+}
+.mobile-menu a:hover,
+.mobile-menu a.accent { color: var(--accent); }
+
+/* ============================================================
+ BUTTONS
+ ============================================================ */
+.btn-primary {
+ font-family: var(--mono);
+ font-size: 11px;
+ background: var(--accent);
+ color: var(--bg);
+ padding: 13px 30px;
+ border-radius: 3px;
+ text-decoration: none;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ font-weight: 500;
+ transition: all 0.2s;
+ display: inline-block;
+}
+.btn-primary:hover { opacity: 0.85; transform: translateY(-1px); }
+
+.btn-ghost {
+ font-family: var(--mono);
+ font-size: 11px;
+ color: var(--text2);
+ padding: 13px 30px;
+ border-radius: 3px;
+ text-decoration: none;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ border: 1px solid var(--border2);
+ transition: all 0.2s;
+ display: inline-block;
+}
+.btn-ghost:hover { border-color: var(--text2); color: var(--text); }
+
+/* ============================================================
+ HERO
+ ============================================================ */
+.hero {
+ padding: 100px 0 80px;
+ border-bottom: 1px solid var(--border);
+}
+
+.hero-kicker {
+ font-family: var(--mono);
+ font-size: 11px;
+ color: var(--accent);
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.hero-kicker::before {
+ content: '';
+ display: block;
+ width: 28px;
+ height: 1px;
+ background: var(--accent);
+}
+
+.hero-layout {
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr;
+ gap: 5rem;
+ align-items: start;
+}
+
+.hero h1 {
+ font-family: var(--serif);
+ font-size: clamp(40px, 5.5vw, 72px);
+ font-weight: 400;
+ line-height: 1.08;
+ letter-spacing: -0.01em;
+ margin-bottom: 2rem;
+ color: var(--text);
+}
+.hero h1 em { font-style: italic; color: var(--accent); }
+.hero h1 span { color: var(--text2); }
+
+.hero-sub {
+ font-size: 16px;
+ color: var(--text2);
+ line-height: 1.9;
+ margin-bottom: 2.5rem;
+ font-weight: 300;
+ max-width: 480px;
+}
+
+.hero-btns {
+ display: flex;
+ gap: 1rem;
+ flex-wrap: wrap;
+ margin-bottom: 3rem;
+}
+
+/* Hero stats bar */
+.hero-stats {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1px;
+ background: var(--border);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ overflow: hidden;
+}
+
+.h-stat { background: var(--bg2); padding: 1.25rem; text-align: center; }
+
+.h-stat-num {
+ font-family: var(--mono);
+ font-size: 22px;
+ font-weight: 500;
+ color: var(--accent);
+ display: block;
+ line-height: 1;
+}
+
+.h-stat-lbl {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--text3);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ margin-top: 6px;
+ display: block;
+}
+
+/* ============================================================
+ TERMINAL
+ ============================================================ */
+.terminal {
+ background: var(--bg2);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+ font-family: var(--mono);
+}
+
+.t-bar {
+ background: var(--bg3);
+ padding: 10px 16px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ border-bottom: 1px solid var(--border);
+}
+
+.dot { width: 9px; height: 9px; border-radius: 50%; }
+.dot-r { background: #ff5f57; }
+.dot-y { background: #febc2e; }
+.dot-g { background: #28c840; }
+
+.t-title { font-size: 11px; color: var(--text3); margin: 0 auto; }
+
+.t-body { padding: 18px; font-size: 12px; line-height: 1.9; }
+
+/* Terminal token colors */
+.tc { color: #3a4f66; } /* comment */
+.tk { color: #6e9fd4; } /* key */
+.tv { color: var(--accent); } /* value */
+.ts { color: #f4a261; } /* string */
+.tp { color: var(--text3); } /* prompt */
+.tok { color: var(--accent); } /* ok */
+.tw { color: #febc2e; } /* warning */
+.te { color: #ff6b6b; } /* error */
+
+.cursor {
+ display: inline-block;
+ width: 7px;
+ height: 13px;
+ background: var(--accent);
+ vertical-align: middle;
+ animation: blink 1s step-end infinite;
+}
+@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
+
+/* ============================================================
+ TRUST BAR
+ ============================================================ */
+.trust-bar {
+ padding: 24px 0;
+ border-bottom: 1px solid var(--border);
+}
+
+.trust-inner {
+ display: flex;
+ align-items: center;
+ gap: 2.5rem;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.trust-label {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--text4);
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+.trust-logos {
+ display: flex;
+ gap: 1.25rem;
+ flex-wrap: wrap;
+ justify-content: center;
+}
+
+.trust-logo {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--text3);
+ padding: 4px 12px;
+ border: 1px solid var(--border);
+ border-radius: 2px;
+ letter-spacing: 0.05em;
+ transition: all 0.2s;
+}
+.trust-logo:hover { color: var(--text2); border-color: var(--border2); }
+
+/* ============================================================
+ SECTIONS — shared
+ ============================================================ */
+section { padding: 88px 0; }
+
+.section-label {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--accent);
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ margin-bottom: 1.25rem;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.section-label::before {
+ content: '';
+ display: block;
+ width: 14px;
+ height: 1px;
+ background: var(--accent);
+}
+
+.section-title {
+ font-family: var(--serif);
+ font-size: clamp(26px, 3.5vw, 44px);
+ font-weight: 400;
+ line-height: 1.15;
+ margin-bottom: 1rem;
+ letter-spacing: -0.01em;
+}
+.section-title em { font-style: italic; color: var(--accent); }
+
+.section-sub {
+ color: var(--text2);
+ font-size: 15px;
+ font-weight: 300;
+ max-width: 520px;
+ line-height: 1.9;
+}
+
+/* ============================================================
+ WHO IS THIS FOR
+ ============================================================ */
+.for-section {
+ background: var(--bg2);
+ border-top: 1px solid var(--border);
+ border-bottom: 1px solid var(--border);
+}
+
+.for-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1px;
+ background: var(--border);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+ margin-top: 3rem;
+}
+
+.for-card {
+ background: var(--bg);
+ padding: 2rem;
+ transition: background 0.2s;
+}
+.for-card:hover { background: var(--bg3); }
+
+.fc-tag {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--accent2);
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ margin-bottom: 0.75rem;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.fc-tag::before {
+ content: '';
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+ background: var(--accent2);
+ display: block;
+ flex-shrink: 0;
+}
+
+.for-card h3 {
+ font-family: var(--serif);
+ font-size: 20px;
+ margin-bottom: 0.75rem;
+ font-weight: 400;
+ line-height: 1.3;
+}
+
+.for-card p {
+ font-size: 13px;
+ color: var(--text2);
+ line-height: 1.85;
+}
+
+/* ============================================================
+ SERVICES
+ ============================================================ */
+.svc-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 1px;
+ background: var(--border);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+ margin-top: 3rem;
+}
+
+.svc {
+ background: var(--bg);
+ padding: 2.25rem;
+ transition: background 0.2s;
+ position: relative;
+}
+.svc:hover { background: var(--bg2); }
+
+.svc-tag {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--accent);
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ margin-bottom: 1rem;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.svc-tag::before {
+ content: '';
+ width: 5px;
+ height: 5px;
+ background: var(--accent);
+ border-radius: 50%;
+ display: block;
+ flex-shrink: 0;
+}
+
+.svc h3 {
+ font-family: var(--serif);
+ font-size: 22px;
+ font-weight: 400;
+ margin-bottom: 0.75rem;
+ line-height: 1.3;
+}
+
+.svc p {
+ font-size: 14px;
+ color: var(--text2);
+ line-height: 1.85;
+ margin-bottom: 1.5rem;
+}
+
+.svc-list { list-style: none; display: flex; flex-direction: column; gap: 7px; }
+
+.svc-list li {
+ font-family: var(--mono);
+ font-size: 11px;
+ color: var(--text3);
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ line-height: 1.5;
+}
+.svc-list li::before { content: '→'; color: var(--accent); flex-shrink: 0; }
+
+.svc-badge {
+ position: absolute;
+ top: 1.5rem;
+ right: 1.5rem;
+ font-family: var(--mono);
+ font-size: 9px;
+ background: var(--accent3);
+ color: var(--bg);
+ padding: 3px 8px;
+ border-radius: 2px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+/* AI Banner */
+.ai-banner {
+ background: var(--bg2);
+ border: 1px solid var(--border2);
+ border-radius: 8px;
+ padding: 2.5rem 3rem;
+ margin-top: 2rem;
+ display: grid;
+ grid-template-columns: 1fr auto;
+ gap: 2rem;
+ align-items: center;
+}
+
+.ai-banner h3 {
+ font-family: var(--serif);
+ font-size: 24px;
+ font-weight: 400;
+ margin-bottom: 0.5rem;
+ line-height: 1.3;
+}
+
+.ai-banner p {
+ font-size: 14px;
+ color: var(--text2);
+ line-height: 1.85;
+ max-width: 520px;
+}
+
+.ai-badge {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--accent);
+ border: 1px solid var(--accent);
+ padding: 6px 14px;
+ border-radius: 3px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+/* ============================================================
+ PROCESS
+ ============================================================ */
+.process-section {
+ background: var(--bg2);
+ border-top: 1px solid var(--border);
+ border-bottom: 1px solid var(--border);
+}
+
+.process-steps {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 2rem;
+ margin-top: 3rem;
+}
+
+.proc-divider {
+ width: 100%;
+ height: 1px;
+ background: var(--border);
+ margin-bottom: 2rem;
+}
+
+.proc-num {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--accent);
+ letter-spacing: 0.1em;
+ margin-bottom: 1rem;
+ display: block;
+}
+
+.proc-step h4 {
+ font-family: var(--serif);
+ font-size: 18px;
+ font-weight: 400;
+ margin-bottom: 0.5rem;
+ line-height: 1.3;
+}
+
+.proc-step p {
+ font-size: 13px;
+ color: var(--text2);
+ line-height: 1.85;
+}
+
+/* ============================================================
+ CASE STUDIES
+ ============================================================ */
+.case-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1px;
+ background: var(--border);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+ margin-top: 3rem;
+}
+
+.case {
+ background: var(--bg);
+ padding: 2.5rem;
+ display: grid;
+ grid-template-columns: 1fr 200px;
+ gap: 3rem;
+ align-items: start;
+ transition: background 0.2s;
+}
+.case:hover { background: var(--bg2); }
+
+.case-tag {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--accent2);
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ margin-bottom: 0.75rem;
+}
+
+.case h3 {
+ font-family: var(--serif);
+ font-size: 22px;
+ font-weight: 400;
+ margin-bottom: 0.75rem;
+ line-height: 1.3;
+}
+
+.case-desc {
+ font-size: 14px;
+ color: var(--text2);
+ line-height: 1.9;
+ margin-bottom: 1.25rem;
+}
+
+.case-chips { display: flex; gap: 0.6rem; flex-wrap: wrap; }
+
+.chip {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--text3);
+ background: var(--bg3);
+ padding: 3px 10px;
+ border-radius: 2px;
+ border: 1px solid var(--border);
+}
+
+.case-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.cs-num {
+ font-family: var(--mono);
+ font-size: 28px;
+ font-weight: 500;
+ color: var(--accent);
+ line-height: 1;
+ display: block;
+}
+
+.cs-lbl {
+ font-family: var(--mono);
+ font-size: 9px;
+ color: var(--text3);
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ margin-top: 4px;
+ display: block;
+}
+
+.case-note {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--text4);
+ font-style: italic;
+ margin-top: 1rem;
+}
+
+/* ============================================================
+ TECH STACK
+ ============================================================ */
+.stack-section {
+ background: var(--bg2);
+ border-top: 1px solid var(--border);
+ border-bottom: 1px solid var(--border);
+}
+
+.stack-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
+ gap: 10px;
+ margin-top: 2.5rem;
+}
+
+.stack-item {
+ background: var(--bg3);
+ border: 1px solid var(--border);
+ border-radius: 5px;
+ padding: 12px 14px;
+ text-align: center;
+ transition: all 0.2s;
+}
+.stack-item:hover { border-color: var(--border2); background: var(--bg); }
+
+.st-name { font-family: var(--mono); font-size: 11px; color: var(--text2); display: block; }
+.st-cat { font-family: var(--mono); font-size: 9px; color: var(--text3); margin-top: 3px; display: block; letter-spacing: 0.05em; }
+
+/* ============================================================
+ PRICING
+ ============================================================ */
+.pricing-grid {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 1px;
+ background: var(--border);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ overflow: hidden;
+ margin-top: 3rem;
+}
+
+.plan { background: var(--bg); padding: 2.25rem; position: relative; }
+.plan.featured { background: var(--bg2); }
+
+.plan-badge {
+ font-family: var(--mono);
+ font-size: 9px;
+ color: var(--bg);
+ background: var(--accent);
+ padding: 3px 10px;
+ border-radius: 2px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ display: inline-block;
+ margin-bottom: 1rem;
+}
+
+.plan h3 {
+ font-family: var(--serif);
+ font-size: 22px;
+ font-weight: 400;
+ margin-bottom: 0.4rem;
+}
+
+.plan-price {
+ font-family: var(--mono);
+ font-size: 12px;
+ color: var(--text2);
+ margin-bottom: 1.5rem;
+}
+.plan-price strong {
+ font-size: 28px;
+ color: var(--text);
+ font-weight: 500;
+ font-family: var(--mono);
+}
+.plan-price .per {
+ font-size: 11px;
+ color: var(--text3);
+}
+
+.plan-items { list-style: none; display: flex; flex-direction: column; gap: 10px; }
+
+.plan-items li {
+ font-size: 13px;
+ color: var(--text2);
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ line-height: 1.5;
+}
+.plan-items li::before {
+ content: '✓';
+ color: var(--accent);
+ font-family: var(--mono);
+ font-size: 11px;
+ flex-shrink: 0;
+}
+
+.plan-cta {
+ display: block;
+ margin-top: 2rem;
+ font-family: var(--mono);
+ font-size: 11px;
+ text-align: center;
+ padding: 11px;
+ border-radius: 3px;
+ text-decoration: none;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ transition: all 0.2s;
+ border: 1px solid var(--border2);
+ color: var(--text2);
+}
+.plan-cta:hover { border-color: var(--accent); color: var(--accent); }
+.plan.featured .plan-cta { background: var(--accent); color: var(--bg); border-color: var(--accent); }
+.plan.featured .plan-cta:hover { opacity: 0.85; }
+
+.pricing-note {
+ font-family: var(--mono);
+ font-size: 11px;
+ color: var(--text3);
+ text-align: center;
+ margin-top: 1.5rem;
+}
+
+/* ============================================================
+ CTA / CONTACT
+ ============================================================ */
+.cta-section {
+ text-align: center;
+ padding: 100px 0;
+ border-top: 1px solid var(--border);
+}
+.cta-section .section-label { justify-content: center; }
+
+.cta-section h2 {
+ font-family: var(--serif);
+ font-size: clamp(32px, 4.5vw, 58px);
+ font-weight: 400;
+ letter-spacing: -0.01em;
+ margin-bottom: 1rem;
+ line-height: 1.1;
+}
+.cta-section h2 em { font-style: italic; color: var(--accent); }
+
+.cta-section p {
+ color: var(--text2);
+ font-size: 16px;
+ margin-bottom: 2.5rem;
+ font-weight: 300;
+ line-height: 1.85;
+}
+
+.cta-row { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
+
+.async-note {
+ font-family: var(--mono);
+ font-size: 11px;
+ color: var(--text3);
+ margin-top: 1.5rem;
+}
+
+/* ============================================================
+ FOOTER
+ ============================================================ */
+footer { border-top: 1px solid var(--border); padding: 2rem 0; }
+
+.footer-inner {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 0 2.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.footer-logo {
+ font-family: var(--mono);
+ font-size: 12px;
+ color: var(--text3);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.footer-logo img { width: 16px; height: 16px; object-fit: contain; display: block; }
+
+.footer-links { display: flex; gap: 1.5rem; flex-wrap: wrap; }
+
+.footer-links a {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--text3);
+ text-decoration: none;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ transition: color 0.2s;
+}
+.footer-links a:hover { color: var(--text2); }
+
+/* ============================================================
+ RESPONSIVE
+ ============================================================ */
+@media (max-width: 900px) {
+ .nav-links, .nav-cta { display: none; }
+ .nav-hamburger { display: flex; }
+ .hero-layout { grid-template-columns: 1fr; gap: 2.5rem; }
+ .hero { padding: 80px 0 60px; }
+ .hero h1 { font-size: 42px; }
+ .for-grid { grid-template-columns: 1fr; }
+ .svc-grid { grid-template-columns: 1fr; }
+ .process-steps { grid-template-columns: 1fr 1fr; gap: 2rem; }
+ .case { grid-template-columns: 1fr; }
+ .case-stats { flex-direction: row; gap: 2rem; }
+ .pricing-grid { grid-template-columns: 1fr; }
+ .ai-banner { grid-template-columns: 1fr; }
+ section { padding: 64px 0; }
+ .cta-section { padding: 80px 0; }
+}
+
+@media (max-width: 560px) {
+ .wrap { padding: 0 1.25rem; }
+ .hero { padding: 60px 0 50px; }
+ .hero h1 { font-size: 34px; }
+ .hero-btns { flex-direction: column; }
+ .btn-primary, .btn-ghost { text-align: center; }
+ .hero-stats { grid-template-columns: repeat(2, 1fr); }
+ .process-steps { grid-template-columns: 1fr; }
+ .cta-row { flex-direction: column; align-items: center; }
+ .ai-banner { padding: 1.75rem; }
+ .case { padding: 1.75rem; }
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..aa15214
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "strict": true,
+ "skipLibCheck": true,
+ "types": ["vite/client", "node"]
+ },
+ "include": ["src/**/*.ts", "vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..fd71c31
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,16 @@
+import { resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+import { defineConfig } from "vite";
+
+const rootDir = fileURLToPath(new URL(".", import.meta.url));
+
+export default defineConfig({
+ build: {
+ rollupOptions: {
+ input: {
+ main: resolve(rootDir, "index.html"),
+ contact: resolve(rootDir, "contact.html")
+ }
+ }
+ }
+});
From 01f22818a41515d6baa26e2c40a97b6f5304c62e Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:05:36 -0700
Subject: [PATCH 02/18] Migrate site to Next.js with static export for GitHub
Pages
- Replace Vite/TypeScript setup with Next.js 14 App Router
- Convert index.html + contact.html to React/TSX components
- Add globals.css preserving all original design tokens and styles
- Add Nav client component with mobile menu and scroll highlighting
- Configure next.config.js for static export (output: 'export')
- Move images to public/ for Next.js asset serving
- Update GitHub Actions workflow artifact path from dist to out
- Set metadataBase to testopseng.com for OG/Twitter image resolution
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
.github/workflows/pages.yml | 4 +-
.gitignore | 21 +-
README.md | 1 -
contact.html | 139 --
index.html | 1478 -----------------
next.config.js | 10 +
package-lock.json | 1249 ++++----------
package.json | 24 +-
test_ops_logo.png => public/test_ops_logo.png | Bin
testops_image.png => public/testops_image.png | Bin
src/app/contact/page.tsx | 120 ++
src/{styles/index.css => app/globals.css} | 226 +--
src/app/layout.tsx | 57 +
src/app/page.tsx | 409 +++++
src/components/Nav.tsx | 83 +
src/scripts/contact.ts | 1 -
src/scripts/index.ts | 50 -
src/styles/contact.css | 37 -
tsconfig.json | 22 +-
vite.config.ts | 16 -
20 files changed, 1164 insertions(+), 2783 deletions(-)
delete mode 100644 README.md
delete mode 100644 contact.html
delete mode 100644 index.html
create mode 100644 next.config.js
rename test_ops_logo.png => public/test_ops_logo.png (100%)
rename testops_image.png => public/testops_image.png (100%)
create mode 100644 src/app/contact/page.tsx
rename src/{styles/index.css => app/globals.css} (85%)
create mode 100644 src/app/layout.tsx
create mode 100644 src/app/page.tsx
create mode 100644 src/components/Nav.tsx
delete mode 100644 src/scripts/contact.ts
delete mode 100644 src/scripts/index.ts
delete mode 100644 src/styles/contact.css
delete mode 100644 vite.config.ts
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 53febc0..3cba00b 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -1,4 +1,4 @@
-name: Deploy static site to Pages
+name: Deploy Next.js site to Pages
on:
push:
@@ -44,7 +44,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
- path: ./dist
+ path: ./out
deploy:
needs: build
diff --git a/.gitignore b/.gitignore
index da669ef..fdc6e97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,16 @@
-dist/
-node_modules/
-
-# OS / editor
+/node_modules
+/.pnp
+.pnp.js
+/coverage
+/.next/
+/out/
+/build
.DS_Store
-Thumbs.db
-.vscode/
+*.pem
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.env*.local
+.vercel
+*.tsbuildinfo
+next-env.d.ts
diff --git a/README.md b/README.md
deleted file mode 100644
index 6dfb94e..0000000
--- a/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# TestOpsEngineering
diff --git a/contact.html b/contact.html
deleted file mode 100644
index 52949d0..0000000
--- a/contact.html
+++ /dev/null
@@ -1,139 +0,0 @@
-
-
-
-
-
-
-
-Contact — TestOps Engineering
-
-
-
-
-
-
-
-
-
-
-
- Contact
- Tell me about your QA goals
- Share your current setup, problems, and timeline. I'll review and send back a scoped recommendation and quote.
- Before publishing: replace YOUR_EMAIL@example.com in the form action with your email.
-
-
-
-
-
-
-
-
diff --git a/index.html b/index.html
deleted file mode 100644
index 5b6ca20..0000000
--- a/index.html
+++ /dev/null
@@ -1,1478 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-TestOps Engineering — QA & Automation Consulting
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Quality Engineering · Test Automation · AI-Assisted Testing
-
-
-
-
-
- Your team ships fast.
- We make sure it doesn't break in production.
-
-
- TestOps Engineering brings senior-level QA strategy, automation, and AI-assisted testing
- to startups and growing tech teams — without the overhead of a full-time hire.
-
-
-
-
-
-
-
-
-
-
-
-
-
testops-audit v2 — zsh
-
-
-
$ testops audit --ai-scan --full
-
# Scanning coverage layers...
-
unit_coverage 87% ✓ healthy
-
api_coverage 34% ⚠ gap detected
-
e2e_flakiness 22% ✗ critical
-
# AI model test coverage...
-
llm_eval_tests none ✗ missing
-
prompt_regress none ✗ missing
-
# CI pipeline analysis...
-
avg_build_time 18m 43s ⚠ slow
-
parallelization none ✗ opportunity
-
✓ Roadmap generated — 12 findings
-
$
-
-
-
-
-
-
-
-
-
-
-
-
-
Engineering background from
-
- Google · Pixel
- Meta
- Apple
- Samsung
- Hulu
-
-
-
-
-
-
-
-
-
Who this is for
-
Built for teams that are moving fast and need quality to keep up
-
Not every team needs a full-time QA lead. Many just need someone who's done it at scale — and can get them there without the overhead.
-
-
-
-
Startups · Seed–Series B
-
You're shipping fast with no QA process
-
Your engineers test their own code, CI is slow or flaky, and you've had a production incident you shouldn't have. You need QA infrastructure built from scratch — right the first time.
-
-
-
-
Mid-size Tech · 50–500 eng
-
Your automation exists, but it's not working
-
You have tests, but they're flaky, slow, or nobody trusts them. Your CI pipeline is a black box. You need someone to audit, fix, and hand it back — properly documented and owned by your team.
-
-
-
-
AI & ML Product Teams
-
You're shipping AI features with no eval strategy
-
Your product has LLM-powered features, but there's no regression strategy for prompts or model behavior. You need AI-aware QA — a fast-growing gap most consultants can't fill.
-
-
-
-
-
-
-
-
-
-
Services
-
Everything from audit to full automation build
-
Engagements are scoped, time-boxed, and documented. Your team owns everything after handoff — no dependency on us.
-
-
-
-
-
QA Audit & Roadmap
-
Current-state assessment
-
A structured review of your test pyramid, CI pipeline, and coverage gaps — with a prioritized 30/60/90-day plan your engineering team can actually execute.
-
- Test pyramid health check
- CI/CD signal analysis
- Risk-based coverage gap mapping
- Prioritized improvement roadmap
-
-
-
-
-
Test Automation
-
Frameworks built to last
-
From API contract tests to full E2E suites. Designed for maintainability, parallelization, and fast feedback — not just to check a box.
-
- API + UI automation (Python, Appium)
- CI integration & quality gates
- Parallelization & reporting
- Android / ADB tooling
-
-
-
-
-
Release Quality & Reliability
-
Stop flaky tests killing CI
-
Systematic flakiness reduction, stabilization, and observability improvements so your CI signal means something — and your team trusts it again.
-
- Flaky test triage & quarantine
- Root-cause debugging
- Failure observability setup
- Stabilization backlog planning
-
-
-
-
-
In demand 2026
-
AI & LLM Testing
-
Eval strategy for AI-powered products
-
Most teams shipping AI features have no regression strategy for prompt behavior or model outputs. We build eval pipelines, prompt regression suites, and LLM quality gates.
-
- LLM eval framework design
- Prompt regression testing
- Model output validation
- AI feature QA strategy
-
-
-
-
-
-
-
-
-
AI-assisted testing is not a trend — it's the new baseline
-
89% of engineering orgs are piloting AI in their QA processes in 2026. Only 15% have done it right. We help teams bridge that gap with pragmatic, production-ready AI testing strategies — not vendor hype.
-
-
AI-ready practice
-
-
-
-
-
-
-
-
-
How it works
-
From first call to shipped improvement
-
A focused engagement model that respects your sprint calendar and delivers results in weeks, not quarters.
-
-
-
-
-
01 · Discovery
-
Free 30-minute call
-
We learn your stack, your pain points, and your timeline. No prep deck required — just bring your problems.
-
-
-
-
-
02 · Proposal
-
Scoped, fixed-price plan
-
Clear deliverables, timeline, and price. No hourly billing. No scope surprises. You approve before any work begins.
-
-
-
-
-
03 · Engagement
-
Async-first, embedded work
-
Weekly check-ins. We work in your tools — Jira, Slack, GitHub. No overhead. Just execution.
-
-
-
-
-
04 · Handoff
-
Full docs, your team owns it
-
Everything documented. No dependency on us. Your team runs it forward independently from day one.
-
-
-
-
-
-
-
-
-
-
Selected work
-
Real problems,real outcomes
-
Across Google Pixel, Meta, Apple, Samsung, and Hulu. These are the kinds of problems we solve.
-
-
-
-
-
-
Mobile · Reliability · CI
-
Reduced flaky test rate from 22% to under 3%
-
A mobile hardware team shipping weekly was blocked by unreliable CI — false failures eating 6+ hours of eng time per sprint. Introduced a quarantine policy, stabilization backlog, and diagnostic reruns with structured logging. Eliminated two release-blocking incidents in the first month.
-
- Android · ADB
- Python
- CI/CD
- Mobile hardware
-
-
* Replace these metrics with your real numbers before going live
-
-
-
- −86%
- Flakiness rate
-
-
- 2×
- Release velocity
-
-
- 6h
- Saved per sprint
-
-
-
-
-
-
-
API Coverage · Regression
-
Built API test coverage catching regressions missed by UI-only suite
-
UI-only test suite was missing regressions in backend flows. Built a contract testing layer with data setup helpers and CI quality gates. Caught 3 production-bound bugs in the first week — before they reached staging or customers.
-
- Postman
- Charles Proxy
- Python
- API testing
-
-
* Replace these metrics with your real numbers before going live
-
-
-
- +61%
- API coverage
-
-
- 3
- Bugs caught wk 1
-
-
-
-
-
-
-
-
-
-
-
-
Tech stack
-
Tools we work with
-
Add or remove anything that doesn't match your actual stack before going live.
-
-
Python Automation
-
Appium Mobile UI
-
ADB Android
-
Charles Proxy Network
-
Jenkins CI/CD
-
GitHub Actions CI/CD
-
-
Android Studio Mobile
-
LLM Evals AI testing
-
-
-
-
-
-
-
-
Pricing
-
Scoped engagements,custom quote
-
Every project is different. Share your goals through the Contact page and I'll send a tailored scope and quote.
-
-
-
-
-
QA Audit
-
- 1-week engagement
- Test pyramid assessment
- CI/CD pipeline review
- Written findings report
- 30/60/90-day roadmap
- 1 follow-up call included
-
-
Contact for quote →
-
-
-
-
Most popular
-
Automation Build
-
- 2–4 week engagement
- Framework design & build
- API + UI test coverage
- CI integration + quality gates
- Full handoff & documentation
- 30 days async support
-
-
Contact for quote →
-
-
-
-
AI & LLM Testing
-
- LLM eval framework setup
- Prompt regression test coverage
- Model output validation criteria
- Safety and edge-case test design
- CI quality gates for AI features
- Actionable reporting for releases
-
-
Contact for quote →
-
-
-
-
-
Not sure which engagement fits? Use the Contact page and I'll recommend the best starting point.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/next.config.js b/next.config.js
new file mode 100644
index 0000000..4cde5e1
--- /dev/null
+++ b/next.config.js
@@ -0,0 +1,10 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ output: 'export',
+ trailingSlash: true,
+ images: {
+ unoptimized: true,
+ },
+}
+
+module.exports = nextConfig
diff --git a/package-lock.json b/package-lock.json
index 92e5098..aa477e2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,907 +1,300 @@
{
- "name": "testopsengineering-site",
+ "name": "testops-engineering",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "testopsengineering-site",
+ "name": "testops-engineering",
"version": "1.0.0",
- "devDependencies": {
- "@types/node": "^24.3.0",
- "typescript": "^5.9.2",
- "vite": "^7.1.3"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
- "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
- "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
- "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
- "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
- "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
- "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
- "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
- "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
- "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
- "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
- "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
- "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
- "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
- "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
- "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
- "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
- "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
- "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
- "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
- "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openharmony-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
- "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
- "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
- "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
- "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
- "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
- "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
- "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
- "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
- "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
- "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
- "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
- "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
- "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "dependencies": {
+ "next": "^14.2.0",
+ "react": "^18.3.0",
+ "react-dom": "^18.3.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "typescript": "^5"
+ }
},
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
- "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "node_modules/@next/env": {
+ "version": "14.2.35",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz",
+ "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==",
+ "license": "MIT"
},
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
- "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz",
+ "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
- "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
- "cpu": [
- "loong64"
+ "darwin"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
- "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz",
+ "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==",
"cpu": [
- "loong64"
+ "x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
- "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
- "cpu": [
- "ppc64"
+ "darwin"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
- "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz",
+ "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==",
"cpu": [
- "ppc64"
+ "arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
- "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
- "cpu": [
- "riscv64"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
- "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz",
+ "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==",
"cpu": [
- "riscv64"
+ "arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
- "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
- "cpu": [
- "s390x"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
- "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz",
+ "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
- "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
- "cpu": [
- "x64"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
- "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz",
+ "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
- "openbsd"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
- "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
- "cpu": [
- "arm64"
+ "linux"
],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
- "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz",
+ "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==",
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
- ]
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
- "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz",
+ "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==",
"cpu": [
"ia32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
- ]
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
},
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
- "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.33",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz",
+ "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==",
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
- "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
- "cpu": [
- "x64"
],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
+ "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.43",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
+ "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
"dev": true,
"license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
},
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
"dev": true,
"license": "MIT"
},
- "node_modules/@types/node": {
- "version": "24.13.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
- "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
+ "node_modules/@types/react": {
+ "version": "18.3.31",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
+ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.18.0"
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
}
},
- "node_modules/esbuild": {
- "version": "0.28.1",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
- "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
- "hasInstallScript": true,
"license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
},
"engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.28.1",
- "@esbuild/android-arm": "0.28.1",
- "@esbuild/android-arm64": "0.28.1",
- "@esbuild/android-x64": "0.28.1",
- "@esbuild/darwin-arm64": "0.28.1",
- "@esbuild/darwin-x64": "0.28.1",
- "@esbuild/freebsd-arm64": "0.28.1",
- "@esbuild/freebsd-x64": "0.28.1",
- "@esbuild/linux-arm": "0.28.1",
- "@esbuild/linux-arm64": "0.28.1",
- "@esbuild/linux-ia32": "0.28.1",
- "@esbuild/linux-loong64": "0.28.1",
- "@esbuild/linux-mips64el": "0.28.1",
- "@esbuild/linux-ppc64": "0.28.1",
- "@esbuild/linux-riscv64": "0.28.1",
- "@esbuild/linux-s390x": "0.28.1",
- "@esbuild/linux-x64": "0.28.1",
- "@esbuild/netbsd-arm64": "0.28.1",
- "@esbuild/netbsd-x64": "0.28.1",
- "@esbuild/openbsd-arm64": "0.28.1",
- "@esbuild/openbsd-x64": "0.28.1",
- "@esbuild/openharmony-arm64": "0.28.1",
- "@esbuild/sunos-x64": "0.28.1",
- "@esbuild/win32-arm64": "0.28.1",
- "@esbuild/win32-ia32": "0.28.1",
- "@esbuild/win32-x64": "0.28.1"
+ "node": ">=10.16.0"
}
},
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001806",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
+ "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
- }
+ ],
+ "license": "CC-BY-4.0"
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
- "hasInstallScript": true,
+ "license": "MIT"
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
}
},
"node_modules/nanoid": {
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -916,31 +309,66 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/next": {
+ "version": "14.2.35",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz",
+ "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "14.2.35",
+ "@swc/helpers": "0.5.5",
+ "busboy": "1.6.0",
+ "caniuse-lite": "^1.0.30001579",
+ "graceful-fs": "^4.2.11",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.1"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=18.17.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "14.2.33",
+ "@next/swc-darwin-x64": "14.2.33",
+ "@next/swc-linux-arm64-gnu": "14.2.33",
+ "@next/swc-linux-arm64-musl": "14.2.33",
+ "@next/swc-linux-x64-gnu": "14.2.33",
+ "@next/swc-linux-x64-musl": "14.2.33",
+ "@next/swc-win32-arm64-msvc": "14.2.33",
+ "@next/swc-win32-ia32-msvc": "14.2.33",
+ "@next/swc-win32-x64-msvc": "14.2.33"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
- "node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
"node_modules/postcss": {
- "version": "8.5.20",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz",
- "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==",
- "dev": true,
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
@@ -957,86 +385,94 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.16",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/rollup": {
- "version": "4.62.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
- "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
- "dev": true,
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"dependencies": {
- "@types/estree": "1.0.9"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
+ "loose-envify": "^1.1.0"
},
"engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
},
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.62.2",
- "@rollup/rollup-android-arm64": "4.62.2",
- "@rollup/rollup-darwin-arm64": "4.62.2",
- "@rollup/rollup-darwin-x64": "4.62.2",
- "@rollup/rollup-freebsd-arm64": "4.62.2",
- "@rollup/rollup-freebsd-x64": "4.62.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
- "@rollup/rollup-linux-arm64-gnu": "4.62.2",
- "@rollup/rollup-linux-arm64-musl": "4.62.2",
- "@rollup/rollup-linux-loong64-gnu": "4.62.2",
- "@rollup/rollup-linux-loong64-musl": "4.62.2",
- "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
- "@rollup/rollup-linux-ppc64-musl": "4.62.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
- "@rollup/rollup-linux-riscv64-musl": "4.62.2",
- "@rollup/rollup-linux-s390x-gnu": "4.62.2",
- "@rollup/rollup-linux-x64-gnu": "4.62.2",
- "@rollup/rollup-linux-x64-musl": "4.62.2",
- "@rollup/rollup-openbsd-x64": "4.62.2",
- "@rollup/rollup-openharmony-arm64": "4.62.2",
- "@rollup/rollup-win32-arm64-msvc": "4.62.2",
- "@rollup/rollup-win32-ia32-msvc": "4.62.2",
- "@rollup/rollup-win32-x64-gnu": "4.62.2",
- "@rollup/rollup-win32-x64-msvc": "4.62.2",
- "fsevents": "~2.3.2"
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
- "node_modules/tinyglobby": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
- "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
- "dev": true,
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
+ "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
"license": "MIT",
"dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.4"
+ "client-only": "0.0.1"
},
"engines": {
- "node": ">=12.0.0"
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
},
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
@@ -1052,86 +488,11 @@
}
},
"node_modules/undici-types": {
- "version": "7.18.2",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
- "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
- },
- "node_modules/vite": {
- "version": "7.3.6",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
- "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "esbuild": "^0.27.0 || ^0.28.0",
- "fdir": "^6.5.0",
- "picomatch": "^4.0.3",
- "postcss": "^8.5.6",
- "rollup": "^4.43.0",
- "tinyglobby": "^0.2.15"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^20.19.0 || >=22.12.0",
- "jiti": ">=1.21.0",
- "less": "^4.0.0",
- "lightningcss": "^1.21.0",
- "sass": "^1.70.0",
- "sass-embedded": "^1.70.0",
- "stylus": ">=0.54.8",
- "sugarss": "^5.0.0",
- "terser": "^5.16.0",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "jiti": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "sass-embedded": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
}
}
}
diff --git a/package.json b/package.json
index 52003fb..88d70e1 100644
--- a/package.json
+++ b/package.json
@@ -1,17 +1,21 @@
{
- "name": "testopsengineering-site",
- "private": true,
+ "name": "testops-engineering",
"version": "1.0.0",
- "type": "module",
+ "private": true,
"scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview",
- "typecheck": "tsc --noEmit"
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start"
+ },
+ "dependencies": {
+ "next": "^14.2.0",
+ "react": "^18.3.0",
+ "react-dom": "^18.3.0"
},
"devDependencies": {
- "@types/node": "^24.3.0",
- "typescript": "^5.9.2",
- "vite": "^7.1.3"
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "typescript": "^5"
}
}
diff --git a/test_ops_logo.png b/public/test_ops_logo.png
similarity index 100%
rename from test_ops_logo.png
rename to public/test_ops_logo.png
diff --git a/testops_image.png b/public/testops_image.png
similarity index 100%
rename from testops_image.png
rename to public/testops_image.png
diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx
new file mode 100644
index 0000000..8ffc6c9
--- /dev/null
+++ b/src/app/contact/page.tsx
@@ -0,0 +1,120 @@
+import type { Metadata } from 'next'
+import Image from 'next/image'
+import Link from 'next/link'
+
+export const metadata: Metadata = {
+ title: 'Contact — TestOps Engineering',
+ description: 'Contact TestOps Engineering for a scoped QA consulting quote.',
+ openGraph: { images: [{ url: '/testops_image.png' }] },
+}
+
+export default function Contact() {
+ return (
+ <>
+
+
+
+
+ Contact
+ Tell me about your QA goals
+
+ Share your current setup, problems, and timeline. I'll review and send back a
+ scoped recommendation and quote.
+
+
+ Before publishing: replace YOUR_EMAIL@example.com in the form action
+ with your email.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Company
+
+
+
+ Company size
+
+ Select one
+ 1–10
+ 11–50
+ 51–200
+ 201–500
+ 500+
+
+
+
+
+
+
+ Service needed
+
+ Select one
+ QA Audit & Roadmap
+ Automation Build
+ Release Reliability / Flakiness
+ AI & LLM Testing
+
+
+
+ Timeline
+
+ ASAP
+ Within 30 days
+ 1–3 months
+ Just exploring
+
+
+
+
+
+
+ What are you trying to improve?
+
+
+
+
+ Send inquiry
+
+
+
+
+ >
+ )
+}
diff --git a/src/styles/index.css b/src/app/globals.css
similarity index 85%
rename from src/styles/index.css
rename to src/app/globals.css
index 3362f07..7d45043 100644
--- a/src/styles/index.css
+++ b/src/app/globals.css
@@ -1,6 +1,3 @@
-/* ============================================================
- RESET & VARIABLES
- ============================================================ */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
@@ -18,9 +15,9 @@
--text2: #7a8fa8;
--text3: #3d4f63;
--text4: #1e2d3d;
- --mono: 'DM Mono', monospace;
- --serif: 'Instrument Serif', serif;
- --body: 'DM Sans', sans-serif;
+ --mono: var(--font-mono), monospace;
+ --serif: var(--font-serif), serif;
+ --body: var(--font-body), sans-serif;
}
html { scroll-behavior: smooth; }
@@ -34,14 +31,10 @@ body {
overflow-x: hidden;
}
-/* ============================================================
- LAYOUT
- ============================================================ */
+/* LAYOUT */
.wrap { max-width: 1100px; margin: 0 auto; padding: 0 2.5rem; }
-/* ============================================================
- NAV
- ============================================================ */
+/* NAV */
nav {
position: sticky;
top: 0;
@@ -74,15 +67,6 @@ nav {
}
.logo img { width: 34px; height: 34px; object-fit: contain; display: block; }
-.logo-dot {
- width: 7px;
- height: 7px;
- background: var(--accent);
- border-radius: 50%;
- display: block;
- flex-shrink: 0;
-}
-
.nav-right { display: flex; align-items: center; gap: 2rem; }
.nav-links { display: flex; gap: 2rem; list-style: none; }
@@ -114,7 +98,6 @@ nav {
}
.nav-cta:hover { opacity: 0.85; }
-/* Hamburger */
.nav-hamburger {
display: none;
flex-direction: column;
@@ -132,7 +115,6 @@ nav {
transition: all 0.2s;
}
-/* Mobile menu */
.mobile-menu {
display: none;
position: fixed;
@@ -160,9 +142,7 @@ nav {
.mobile-menu a:hover,
.mobile-menu a.accent { color: var(--accent); }
-/* ============================================================
- BUTTONS
- ============================================================ */
+/* BUTTONS */
.btn-primary {
font-family: var(--mono);
font-size: 11px;
@@ -194,9 +174,7 @@ nav {
}
.btn-ghost:hover { border-color: var(--text2); color: var(--text); }
-/* ============================================================
- HERO
- ============================================================ */
+/* HERO */
.hero {
padding: 100px 0 80px;
border-bottom: 1px solid var(--border);
@@ -256,7 +234,6 @@ nav {
margin-bottom: 3rem;
}
-/* Hero stats bar */
.hero-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
@@ -288,9 +265,7 @@ nav {
display: block;
}
-/* ============================================================
- TERMINAL
- ============================================================ */
+/* TERMINAL */
.terminal {
background: var(--bg2);
border: 1px solid var(--border);
@@ -317,15 +292,14 @@ nav {
.t-body { padding: 18px; font-size: 12px; line-height: 1.9; }
-/* Terminal token colors */
-.tc { color: #3a4f66; } /* comment */
-.tk { color: #6e9fd4; } /* key */
-.tv { color: var(--accent); } /* value */
-.ts { color: #f4a261; } /* string */
-.tp { color: var(--text3); } /* prompt */
-.tok { color: var(--accent); } /* ok */
-.tw { color: #febc2e; } /* warning */
-.te { color: #ff6b6b; } /* error */
+.tc { color: #3a4f66; }
+.tk { color: #6e9fd4; }
+.tv { color: var(--accent); }
+.ts { color: #f4a261; }
+.tp { color: var(--text3); }
+.tok { color: var(--accent); }
+.tw { color: #febc2e; }
+.te { color: #ff6b6b; }
.cursor {
display: inline-block;
@@ -337,9 +311,7 @@ nav {
}
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
-/* ============================================================
- TRUST BAR
- ============================================================ */
+/* TRUST BAR */
.trust-bar {
padding: 24px 0;
border-bottom: 1px solid var(--border);
@@ -381,9 +353,7 @@ nav {
}
.trust-logo:hover { color: var(--text2); border-color: var(--border2); }
-/* ============================================================
- SECTIONS — shared
- ============================================================ */
+/* SECTIONS shared */
section { padding: 88px 0; }
.section-label {
@@ -423,9 +393,7 @@ section { padding: 88px 0; }
line-height: 1.9;
}
-/* ============================================================
- WHO IS THIS FOR
- ============================================================ */
+/* WHO IS THIS FOR */
.for-section {
background: var(--bg2);
border-top: 1px solid var(--border);
@@ -485,9 +453,7 @@ section { padding: 88px 0; }
line-height: 1.85;
}
-/* ============================================================
- SERVICES
- ============================================================ */
+/* SERVICES */
.svc-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
@@ -570,7 +536,6 @@ section { padding: 88px 0; }
text-transform: uppercase;
}
-/* AI Banner */
.ai-banner {
background: var(--bg2);
border: 1px solid var(--border2);
@@ -610,9 +575,7 @@ section { padding: 88px 0; }
white-space: nowrap;
}
-/* ============================================================
- PROCESS
- ============================================================ */
+/* PROCESS */
.process-section {
background: var(--bg2);
border-top: 1px solid var(--border);
@@ -656,9 +619,7 @@ section { padding: 88px 0; }
line-height: 1.85;
}
-/* ============================================================
- CASE STUDIES
- ============================================================ */
+/* CASE STUDIES */
.case-list {
display: flex;
flex-direction: column;
@@ -750,9 +711,7 @@ section { padding: 88px 0; }
margin-top: 1rem;
}
-/* ============================================================
- TECH STACK
- ============================================================ */
+/* TECH STACK */
.stack-section {
background: var(--bg2);
border-top: 1px solid var(--border);
@@ -779,9 +738,7 @@ section { padding: 88px 0; }
.st-name { font-family: var(--mono); font-size: 11px; color: var(--text2); display: block; }
.st-cat { font-family: var(--mono); font-size: 9px; color: var(--text3); margin-top: 3px; display: block; letter-spacing: 0.05em; }
-/* ============================================================
- PRICING
- ============================================================ */
+/* PRICING */
.pricing-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
@@ -816,23 +773,6 @@ section { padding: 88px 0; }
margin-bottom: 0.4rem;
}
-.plan-price {
- font-family: var(--mono);
- font-size: 12px;
- color: var(--text2);
- margin-bottom: 1.5rem;
-}
-.plan-price strong {
- font-size: 28px;
- color: var(--text);
- font-weight: 500;
- font-family: var(--mono);
-}
-.plan-price .per {
- font-size: 11px;
- color: var(--text3);
-}
-
.plan-items { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.plan-items li {
@@ -878,9 +818,7 @@ section { padding: 88px 0; }
margin-top: 1.5rem;
}
-/* ============================================================
- CTA / CONTACT
- ============================================================ */
+/* CTA / CONTACT */
.cta-section {
text-align: center;
padding: 100px 0;
@@ -915,9 +853,7 @@ section { padding: 88px 0; }
margin-top: 1.5rem;
}
-/* ============================================================
- FOOTER
- ============================================================ */
+/* FOOTER */
footer { border-top: 1px solid var(--border); padding: 2rem 0; }
.footer-inner {
@@ -954,9 +890,109 @@ footer { border-top: 1px solid var(--border); padding: 2rem 0; }
}
.footer-links a:hover { color: var(--text2); }
-/* ============================================================
- RESPONSIVE
- ============================================================ */
+/* CONTACT PAGE */
+.contact-header { border-bottom: 1px solid var(--border); }
+.contact-top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1rem 0;
+}
+.brand {
+ color: var(--text);
+ text-decoration: none;
+ font-family: var(--mono);
+ font-size: 13px;
+ letter-spacing: 0.03em;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+.brand img { width: 16px; height: 16px; object-fit: contain; }
+.back {
+ color: var(--text2);
+ text-decoration: none;
+ font-family: var(--mono);
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+.contact-hero { padding: 3rem 0 1.5rem; }
+.kicker {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--accent);
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ margin-bottom: 0.9rem;
+}
+.contact-hero h1 {
+ font-family: var(--serif);
+ font-weight: 400;
+ font-size: clamp(34px, 5vw, 54px);
+ line-height: 1.1;
+ margin-bottom: 0.8rem;
+}
+.contact-sub { color: var(--text2); max-width: 620px; }
+.contact-note { margin-top: 1rem; font-size: 12px; color: #f5a623; font-family: var(--mono); }
+.contact-form {
+ background: var(--bg2);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin: 2rem 0 3rem;
+}
+.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
+.form-row { margin-bottom: 1rem; }
+.form-label {
+ display: block;
+ margin-bottom: 0.35rem;
+ color: var(--text2);
+ font-family: var(--mono);
+ font-size: 11px;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+}
+.form-input,
+.form-select,
+.form-textarea {
+ width: 100%;
+ background: var(--bg3);
+ border: 1px solid var(--border);
+ color: var(--text);
+ border-radius: 4px;
+ padding: 0.72rem 0.85rem;
+ font-family: var(--body);
+ font-size: 14px;
+}
+.form-input:focus,
+.form-select:focus,
+.form-textarea:focus { outline: none; border-color: var(--border2); }
+.form-textarea { min-height: 140px; resize: vertical; }
+.form-btn {
+ display: inline-block;
+ margin-top: 0.5rem;
+ background: var(--accent);
+ color: var(--bg);
+ border: 0;
+ padding: 0.8rem 1rem;
+ border-radius: 3px;
+ cursor: pointer;
+ font-family: var(--mono);
+ font-size: 11px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+.form-btn:hover { opacity: 0.9; }
+.contact-footer {
+ border-top: 1px solid var(--border);
+ padding: 1rem 0 2rem;
+ color: var(--text2);
+ font-family: var(--mono);
+ font-size: 11px;
+}
+
+/* RESPONSIVE */
@media (max-width: 900px) {
.nav-links, .nav-cta { display: none; }
.nav-hamburger { display: flex; }
@@ -974,6 +1010,10 @@ footer { border-top: 1px solid var(--border); padding: 2rem 0; }
.cta-section { padding: 80px 0; }
}
+@media (max-width: 720px) {
+ .form-grid { grid-template-columns: 1fr; }
+}
+
@media (max-width: 560px) {
.wrap { padding: 0 1.25rem; }
.hero { padding: 60px 0 50px; }
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 0000000..8741d0b
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,57 @@
+import type { Metadata } from 'next'
+import { DM_Mono, Instrument_Serif, DM_Sans } from 'next/font/google'
+import './globals.css'
+
+const dmMono = DM_Mono({
+ weight: ['400', '500'],
+ style: ['normal', 'italic'],
+ subsets: ['latin'],
+ variable: '--font-mono',
+ display: 'swap',
+})
+
+const instrumentSerif = Instrument_Serif({
+ weight: ['400'],
+ style: ['normal', 'italic'],
+ subsets: ['latin'],
+ variable: '--font-serif',
+ display: 'swap',
+})
+
+const dmSans = DM_Sans({
+ weight: ['300', '400', '500'],
+ subsets: ['latin'],
+ variable: '--font-body',
+ display: 'swap',
+})
+
+export const metadata: Metadata = {
+ metadataBase: new URL('https://testopseng.com'),
+ title: 'TestOps Engineering — QA & Automation Consulting',
+ description:
+ 'TestOps Engineering — QA consulting for startups and mid-size tech companies. Test strategy, automation frameworks, AI/LLM testing, and CI/CD quality gates.',
+ keywords:
+ 'testops, qa consulting, test automation, quality engineering, cicd, ai testing, llm testing, software testing',
+ openGraph: {
+ title: 'TestOps Engineering — QA & Automation Consulting',
+ description:
+ 'Senior-level QA strategy, automation, and AI-assisted testing for startups and growing tech teams.',
+ type: 'website',
+ images: [{ url: '/testops_image.png' }],
+ },
+ twitter: {
+ card: 'summary_large_image',
+ images: ['/testops_image.png'],
+ },
+ icons: { icon: '/testops_image.png' },
+}
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+ {children}
+
+
+ )
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644
index 0000000..bb39ef2
--- /dev/null
+++ b/src/app/page.tsx
@@ -0,0 +1,409 @@
+import Image from 'next/image'
+import Link from 'next/link'
+import Nav from '@/components/Nav'
+
+export default function Home() {
+ return (
+ <>
+
+
+ {/* HERO */}
+
+
+
Quality Engineering · Test Automation · AI-Assisted Testing
+
+
+
+ Your team ships fast.
+ We make sure it doesn't break in production.
+
+
+ TestOps Engineering brings senior-level QA strategy, automation, and AI-assisted
+ testing to startups and growing tech teams — without the overhead of a full-time hire.
+
+
+
+
+
+
+
+
+
+
+
testops-audit v2 — zsh
+
+
+
$ testops audit --ai-scan --full
+
# Scanning coverage layers...
+
unit_coverage 87% ✓ healthy
+
api_coverage 34% ⚠ gap detected
+
e2e_flakiness 22% ✗ critical
+
# AI model test coverage...
+
llm_eval_tests none ✗ missing
+
prompt_regress none ✗ missing
+
# CI pipeline analysis...
+
avg_build_time 18m 43s ⚠ slow
+
parallelization none ✗ opportunity
+
✓ Roadmap generated — 12 findings
+
$
+
+
+
+
+
+
+
+ {/* TRUST BAR */}
+
+
+
+
Engineering background from
+
+ Google · Pixel
+ Meta
+ Apple
+ Samsung
+ Hulu
+
+
+
+
+
+ {/* WHO IS THIS FOR */}
+
+
+
Who this is for
+
+ Built for teams that are moving fast and need quality to keep up
+
+
+ Not every team needs a full-time QA lead. Many just need someone who's done it at
+ scale — and can get them there without the overhead.
+
+
+
+
Startups · Seed–Series B
+
You're shipping fast with no QA process
+
+ Your engineers test their own code, CI is slow or flaky, and you've had a
+ production incident you shouldn't have. You need QA infrastructure built from
+ scratch — right the first time.
+
+
+
+
Mid-size Tech · 50–500 eng
+
Your automation exists, but it's not working
+
+ You have tests, but they're flaky, slow, or nobody trusts them. Your CI
+ pipeline is a black box. You need someone to audit, fix, and hand it back —
+ properly documented and owned by your team.
+
+
+
+
AI & ML Product Teams
+
You're shipping AI features with no eval strategy
+
+ Your product has LLM-powered features, but there's no regression strategy for
+ prompts or model behavior. You need AI-aware QA — a fast-growing gap most
+ consultants can't fill.
+
+
+
+
+
+
+ {/* SERVICES */}
+
+
+
Services
+
+ Everything from audit to full automation build
+
+
+ Engagements are scoped, time-boxed, and documented. Your team owns everything after
+ handoff — no dependency on us.
+
+
+
+
+
QA Audit & Roadmap
+
Current-state assessment
+
+ A structured review of your test pyramid, CI pipeline, and coverage gaps — with a
+ prioritized 30/60/90-day plan your engineering team can actually execute.
+
+
+ Test pyramid health check
+ CI/CD signal analysis
+ Risk-based coverage gap mapping
+ Prioritized improvement roadmap
+
+
+
+
+
Test Automation
+
Frameworks built to last
+
+ From API contract tests to full E2E suites. Designed for maintainability,
+ parallelization, and fast feedback — not just to check a box.
+
+
+ API + UI automation (Python, Appium)
+ CI integration & quality gates
+ Parallelization & reporting
+ Android / ADB tooling
+
+
+
+
+
Release Quality & Reliability
+
Stop flaky tests killing CI
+
+ Systematic flakiness reduction, stabilization, and observability improvements so
+ your CI signal means something — and your team trusts it again.
+
+
+ Flaky test triage & quarantine
+ Root-cause debugging
+ Failure observability setup
+ Stabilization backlog planning
+
+
+
+
+
In demand 2026
+
AI & LLM Testing
+
Eval strategy for AI-powered products
+
+ Most teams shipping AI features have no regression strategy for prompt behavior or
+ model outputs. We build eval pipelines, prompt regression suites, and LLM quality
+ gates.
+
+
+ LLM eval framework design
+ Prompt regression testing
+ Model output validation
+ AI feature QA strategy
+
+
+
+
+
+
+
AI-assisted testing is not a trend — it's the new baseline
+
+ 89% of engineering orgs are piloting AI in their QA processes in 2026. Only 15%
+ have done it right. We help teams bridge that gap with pragmatic,
+ production-ready AI testing strategies — not vendor hype.
+
+
+
AI-ready practice
+
+
+
+
+ {/* PROCESS */}
+
+
+
How it works
+
+ From first call to shipped improvement
+
+
+ A focused engagement model that respects your sprint calendar and delivers results in
+ weeks, not quarters.
+
+
+ {[
+ { num: '01 · Discovery', title: 'Free 30-minute call', body: "We learn your stack, your pain points, and your timeline. No prep deck required — just bring your problems." },
+ { num: '02 · Proposal', title: 'Scoped, fixed-price plan', body: "Clear deliverables, timeline, and price. No hourly billing. No scope surprises. You approve before any work begins." },
+ { num: '03 · Engagement', title: 'Async-first, embedded work', body: "Weekly check-ins. We work in your tools — Jira, Slack, GitHub. No overhead. Just execution." },
+ { num: '04 · Handoff', title: 'Full docs, your team owns it', body: "Everything documented. No dependency on us. Your team runs it forward independently from day one." },
+ ].map((step) => (
+
+
+
{step.num}
+
{step.title}
+
{step.body}
+
+ ))}
+
+
+
+
+ {/* CASE STUDIES */}
+
+
+
Selected work
+
+ Real problems,real outcomes
+
+
+ Across Google Pixel, Meta, Apple, Samsung, and Hulu. These are the kinds of problems
+ we solve.
+
+
+
+
+
+
Mobile · Reliability · CI
+
Reduced flaky test rate from 22% to under 3%
+
+ A mobile hardware team shipping weekly was blocked by unreliable CI — false
+ failures eating 6+ hours of eng time per sprint. Introduced a quarantine policy,
+ stabilization backlog, and diagnostic reruns with structured logging. Eliminated
+ two release-blocking incidents in the first month.
+
+
+ Android · ADB
+ Python
+ CI/CD
+ Mobile hardware
+
+
* Replace these metrics with your real numbers before going live
+
+
+
−86% Flakiness rate
+
2× Release velocity
+
6h Saved per sprint
+
+
+
+
+
+
API Coverage · Regression
+
Built API test coverage catching regressions missed by UI-only suite
+
+ UI-only test suite was missing regressions in backend flows. Built a contract
+ testing layer with data setup helpers and CI quality gates. Caught 3
+ production-bound bugs in the first week — before they reached staging or
+ customers.
+
+
+ Postman
+ Charles Proxy
+ Python
+ API testing
+
+
* Replace these metrics with your real numbers before going live
+
+
+
+61% API coverage
+
3 Bugs caught wk 1
+
+
+
+
+
+
+ {/* TECH STACK */}
+
+
+
Tech stack
+
Tools we work with
+
+ Add or remove anything that doesn't match your actual stack before going live.
+
+
+ {[
+ { name: 'Python', cat: 'Automation' },
+ { name: 'Appium', cat: 'Mobile UI' },
+ { name: 'ADB', cat: 'Android' },
+ { name: 'Charles Proxy', cat: 'Network' },
+ { name: 'Jenkins', cat: 'CI/CD' },
+ { name: 'GitHub Actions', cat: 'CI/CD' },
+ { name: 'Android Studio', cat: 'Mobile' },
+ { name: 'LLM Evals', cat: 'AI testing' },
+ ].map((item) => (
+
+ {item.name}
+ {item.cat}
+
+ ))}
+
+
+
+
+ {/* PRICING */}
+
+
+
Pricing
+
+ Scoped engagements,custom quote
+
+
+ Every project is different. Share your goals through the Contact page and I'll
+ send a tailored scope and quote.
+
+
+
+
+
QA Audit
+
+ 1-week engagement
+ Test pyramid assessment
+ CI/CD pipeline review
+ Written findings report
+ 30/60/90-day roadmap
+ 1 follow-up call included
+
+
Contact for quote →
+
+
+
+
Most popular
+
Automation Build
+
+ 2–4 week engagement
+ Framework design & build
+ API + UI test coverage
+ CI integration + quality gates
+ Full handoff & documentation
+ 30 days async support
+
+
Contact for quote →
+
+
+
+
AI & LLM Testing
+
+ LLM eval framework setup
+ Prompt regression test coverage
+ Model output validation criteria
+ Safety and edge-case test design
+ CI quality gates for AI features
+ Actionable reporting for releases
+
+
Contact for quote →
+
+
+
+
+ Not sure which engagement fits? Use the Contact page and I'll recommend the best
+ starting point.
+
+
+
+
+ {/* FOOTER */}
+
+ >
+ )
+}
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
new file mode 100644
index 0000000..30642e8
--- /dev/null
+++ b/src/components/Nav.tsx
@@ -0,0 +1,83 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import Image from 'next/image'
+import Link from 'next/link'
+
+export default function Nav() {
+ const [menuOpen, setMenuOpen] = useState(false)
+ const [activeSection, setActiveSection] = useState('')
+
+ useEffect(() => {
+ const handleScroll = () => {
+ const scrollY = window.pageYOffset
+ const sections = document.querySelectorAll('section[id], div[id]')
+ sections.forEach((section) => {
+ const top = section.offsetTop - 80
+ const bottom = top + section.offsetHeight
+ const id = section.getAttribute('id')
+ if (scrollY >= top && scrollY < bottom && id) {
+ setActiveSection(id)
+ }
+ })
+ }
+ window.addEventListener('scroll', handleScroll)
+ return () => window.removeEventListener('scroll', handleScroll)
+ }, [])
+
+ useEffect(() => {
+ const handleOutsideClick = (e: MouseEvent) => {
+ const menu = document.getElementById('mobileMenu')
+ const hamburger = document.querySelector('.nav-hamburger')
+ if (
+ menuOpen &&
+ menu &&
+ hamburger &&
+ !menu.contains(e.target as Node) &&
+ !hamburger.contains(e.target as Node)
+ ) {
+ setMenuOpen(false)
+ }
+ }
+ document.addEventListener('click', handleOutsideClick)
+ return () => document.removeEventListener('click', handleOutsideClick)
+ }, [menuOpen])
+
+ const navColor = (id: string) =>
+ activeSection === id ? 'var(--accent)' : undefined
+
+ return (
+
+
+
+
+ TestOps Engineering
+
+
+
+
Contact
+
setMenuOpen((o) => !o)}
+ aria-label="Toggle navigation"
+ >
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/scripts/contact.ts b/src/scripts/contact.ts
deleted file mode 100644
index fc36296..0000000
--- a/src/scripts/contact.ts
+++ /dev/null
@@ -1 +0,0 @@
-import "../styles/contact.css";
diff --git a/src/scripts/index.ts b/src/scripts/index.ts
deleted file mode 100644
index 9498ec3..0000000
--- a/src/scripts/index.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import "../styles/index.css";
-
-declare global {
- interface Window {
- toggleMenu: () => void;
- closeMenu: () => void;
- }
-}
-
-const mobileMenu = document.getElementById("mobileMenu");
-const hamburger = document.querySelector(".nav-hamburger");
-
-window.toggleMenu = () => {
- mobileMenu?.classList.toggle("open");
-};
-
-window.closeMenu = () => {
- mobileMenu?.classList.remove("open");
-};
-
-document.addEventListener("click", (event) => {
- if (!mobileMenu) return;
- if (!hamburger) return;
- const target = event.target as Node;
- if (!mobileMenu.classList.contains("open")) return;
- if (mobileMenu.contains(target)) return;
- if (hamburger.contains(target)) return;
- mobileMenu.classList.remove("open");
-});
-
-const sections = document.querySelectorAll("section[id], div[id]");
-const navLinks = Array.from(document.querySelectorAll(".nav-links a"))
- .filter((link) => link.getAttribute("href")?.startsWith("#"));
-
-window.addEventListener("scroll", () => {
- const scrollY = window.pageYOffset;
- sections.forEach((section) => {
- const id = section.getAttribute("id");
- if (!id) return;
- const top = section.offsetTop - 80;
- const bottom = top + section.offsetHeight;
- if (scrollY >= top) {
- if (scrollY < bottom) {
- navLinks.forEach((link) => {
- link.style.color = link.getAttribute("href") === `#${id}` ? "var(--accent)" : "";
- });
- }
- }
- });
-});
diff --git a/src/styles/contact.css b/src/styles/contact.css
deleted file mode 100644
index 7f8bf89..0000000
--- a/src/styles/contact.css
+++ /dev/null
@@ -1,37 +0,0 @@
-*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
-:root {
- --bg:#07090c; --bg2:#0e1116; --bg3:#141820;
- --border:#1c2230; --border2:#263040;
- --accent:#00d4a8; --text:#dde4ee; --text2:#7a8fa8;
- --mono:'DM Mono',monospace; --serif:'Instrument Serif',serif; --body:'DM Sans',sans-serif;
-}
-body { background: var(--bg); color: var(--text); font-family: var(--body); line-height: 1.7; }
-.wrap { max-width: 860px; margin: 0 auto; padding: 0 1.5rem; }
-header { border-bottom: 1px solid var(--border); }
-.top { display:flex; justify-content:space-between; align-items:center; padding: 1rem 0; }
-.brand { color: var(--text); text-decoration:none; font-family: var(--mono); font-size: 13px; letter-spacing: .03em; }
-.brand img { width: 16px; height: 16px; object-fit: contain; margin-right:8px; vertical-align:middle; }
-.back { color: var(--text2); text-decoration:none; font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: .08em; }
-.hero { padding: 3rem 0 1.5rem; }
-.kicker { font-family: var(--mono); font-size: 10px; color: var(--accent); letter-spacing:.2em; text-transform: uppercase; margin-bottom: .9rem; }
-h1 { font-family: var(--serif); font-weight: 400; font-size: clamp(34px,5vw,54px); line-height: 1.1; margin-bottom: .8rem; }
-.sub { color: var(--text2); max-width: 620px; }
-.note { margin-top: 1rem; font-size: 12px; color: #f5a623; font-family: var(--mono); }
-form { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin: 2rem 0 3rem; }
-.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
-.row { margin-bottom: 1rem; }
-label { display:block; margin-bottom:.35rem; color: var(--text2); font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; }
-input, select, textarea {
- width:100%; background: var(--bg3); border:1px solid var(--border); color: var(--text);
- border-radius: 4px; padding: .72rem .85rem; font-family: var(--body); font-size: 14px;
-}
-input:focus, select:focus, textarea:focus { outline: none; border-color: var(--border2); }
-textarea { min-height: 140px; resize: vertical; }
-.btn {
- display:inline-block; margin-top:.5rem; background: var(--accent); color: var(--bg);
- border:0; padding: .8rem 1rem; border-radius: 3px; cursor:pointer;
- font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
-}
-.btn:hover { opacity: .9; }
-footer { border-top: 1px solid var(--border); padding: 1rem 0 2rem; color: var(--text2); font-family: var(--mono); font-size: 11px; }
-@media (max-width: 720px) { .grid { grid-template-columns: 1fr; } }
diff --git a/tsconfig.json b/tsconfig.json
index aa15214..1f9a3fb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,11 +1,21 @@
{
"compilerOptions": {
- "target": "ES2020",
- "module": "ESNext",
- "moduleResolution": "Bundler",
- "strict": true,
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
"skipLibCheck": true,
- "types": ["vite/client", "node"]
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [{ "name": "next" }],
+ "paths": { "@/*": ["./src/*"] }
},
- "include": ["src/**/*.ts", "vite.config.ts"]
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
}
diff --git a/vite.config.ts b/vite.config.ts
deleted file mode 100644
index fd71c31..0000000
--- a/vite.config.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { resolve } from "node:path";
-import { fileURLToPath } from "node:url";
-import { defineConfig } from "vite";
-
-const rootDir = fileURLToPath(new URL(".", import.meta.url));
-
-export default defineConfig({
- build: {
- rollupOptions: {
- input: {
- main: resolve(rootDir, "index.html"),
- contact: resolve(rootDir, "contact.html")
- }
- }
- }
-});
From 7e47aaa589fabeb9ef437b91ebce8734dc6f7cd4 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:11:55 -0700
Subject: [PATCH 03/18] Fix Vercel 404: add vercel.json and remove
trailingSlash
- Add vercel.json with explicit outputDirectory pointing to out/
- Remove trailingSlash: true so contact page exports as out/contact.html
instead of out/contact/index.html, enabling clean /contact routing
on both Vercel and GitHub Pages
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
next.config.js | 1 -
vercel.json | 4 ++++
2 files changed, 4 insertions(+), 1 deletion(-)
create mode 100644 vercel.json
diff --git a/next.config.js b/next.config.js
index 4cde5e1..1c8496a 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,7 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export',
- trailingSlash: true,
images: {
unoptimized: true,
},
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..57494a2
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,4 @@
+{
+ "buildCommand": "npm run build",
+ "outputDirectory": "out"
+}
From 046fb3f0f610f84afce40341af1889cc2d35076c Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:14:34 -0700
Subject: [PATCH 04/18] Remove placeholder publishing note from contact page
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/app/contact/page.tsx | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx
index 8ffc6c9..2c6a1ee 100644
--- a/src/app/contact/page.tsx
+++ b/src/app/contact/page.tsx
@@ -29,10 +29,6 @@ export default function Contact() {
Share your current setup, problems, and timeline. I'll review and send back a
scoped recommendation and quote.
-
- Before publishing: replace YOUR_EMAIL@example.com in the form action
- with your email.
-
Date: Tue, 21 Jul 2026 01:23:18 -0700
Subject: [PATCH 05/18] Consistent nav logo + trust bar carousel with company
logos
- Replace contact page custom header with shared Nav component
so logo, size, and navigation are identical on all pages
- Add TrustCarousel component: infinite CSS marquee with Clearbit
logos for Google Pixel, Meta, Apple, Samsung, Hulu, Bank of America,
Gap, and Avid Technology
- Logos rendered white/dim on dark background with hover brightening
- Fade mask on carousel edges for clean entry/exit
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/app/contact/page.tsx | 13 +-----
src/app/globals.css | 72 +++++++++++++++++++++-----------
src/app/page.tsx | 18 ++------
src/components/TrustCarousel.tsx | 38 +++++++++++++++++
4 files changed, 91 insertions(+), 50 deletions(-)
create mode 100644 src/components/TrustCarousel.tsx
diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx
index 2c6a1ee..27f1a98 100644
--- a/src/app/contact/page.tsx
+++ b/src/app/contact/page.tsx
@@ -1,6 +1,5 @@
import type { Metadata } from 'next'
-import Image from 'next/image'
-import Link from 'next/link'
+import Nav from '@/components/Nav'
export const metadata: Metadata = {
title: 'Contact — TestOps Engineering',
@@ -11,15 +10,7 @@ export const metadata: Metadata = {
export default function Contact() {
return (
<>
-
+
diff --git a/src/app/globals.css b/src/app/globals.css
index 7d45043..a8c1794 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -311,47 +311,71 @@ nav {
}
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
-/* TRUST BAR */
+/* TRUST CAROUSEL */
.trust-bar {
- padding: 24px 0;
+ padding: 20px 0;
border-bottom: 1px solid var(--border);
+ overflow: hidden;
}
-.trust-inner {
- display: flex;
- align-items: center;
- gap: 2.5rem;
- flex-wrap: wrap;
- justify-content: center;
-}
-
-.trust-label {
+.trust-carousel-label {
font-family: var(--mono);
font-size: 10px;
color: var(--text4);
letter-spacing: 0.15em;
text-transform: uppercase;
- white-space: nowrap;
+ margin-bottom: 16px;
+}
+
+.trust-carousel-wrap {
+ overflow: hidden;
+ mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
+ -webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
}
-.trust-logos {
+.trust-carousel-track {
display: flex;
- gap: 1.25rem;
- flex-wrap: wrap;
- justify-content: center;
+ align-items: center;
+ gap: 0;
+ width: max-content;
+ animation: trust-scroll 32s linear infinite;
}
+.trust-carousel-track:hover { animation-play-state: paused; }
-.trust-logo {
+.trust-carousel-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 28px;
+ border-right: 1px solid var(--border);
+ white-space: nowrap;
+}
+
+.trust-carousel-logo {
+ width: 22px;
+ height: 22px;
+ object-fit: contain;
+ filter: brightness(0) invert(1) opacity(0.35);
+ transition: filter 0.2s;
+ flex-shrink: 0;
+}
+.trust-carousel-item:hover .trust-carousel-logo {
+ filter: brightness(0) invert(1) opacity(0.75);
+}
+
+.trust-carousel-name {
font-family: var(--mono);
- font-size: 10px;
+ font-size: 11px;
color: var(--text3);
- padding: 4px 12px;
- border: 1px solid var(--border);
- border-radius: 2px;
- letter-spacing: 0.05em;
- transition: all 0.2s;
+ letter-spacing: 0.06em;
+ transition: color 0.2s;
+}
+.trust-carousel-item:hover .trust-carousel-name { color: var(--text2); }
+
+@keyframes trust-scroll {
+ from { transform: translateX(0); }
+ to { transform: translateX(-50%); }
}
-.trust-logo:hover { color: var(--text2); border-color: var(--border2); }
/* SECTIONS shared */
section { padding: 88px 0; }
diff --git a/src/app/page.tsx b/src/app/page.tsx
index bb39ef2..d87d676 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,6 +1,7 @@
import Image from 'next/image'
import Link from 'next/link'
import Nav from '@/components/Nav'
+import TrustCarousel from '@/components/TrustCarousel'
export default function Home() {
return (
@@ -56,21 +57,8 @@ export default function Home() {
- {/* TRUST BAR */}
-
-
-
-
Engineering background from
-
- Google · Pixel
- Meta
- Apple
- Samsung
- Hulu
-
-
-
-
+ {/* TRUST CAROUSEL */}
+
{/* WHO IS THIS FOR */}
diff --git a/src/components/TrustCarousel.tsx b/src/components/TrustCarousel.tsx
new file mode 100644
index 0000000..77dbe83
--- /dev/null
+++ b/src/components/TrustCarousel.tsx
@@ -0,0 +1,38 @@
+const companies = [
+ { name: 'Google · Pixel', domain: 'google.com' },
+ { name: 'Meta', domain: 'meta.com' },
+ { name: 'Apple', domain: 'apple.com' },
+ { name: 'Samsung', domain: 'samsung.com' },
+ { name: 'Hulu', domain: 'hulu.com' },
+ { name: 'Bank of America', domain: 'bankofamerica.com' },
+ { name: 'Gap', domain: 'gap.com' },
+ { name: 'Avid Technology', domain: 'avid.com' },
+]
+
+// Duplicate for seamless infinite loop
+const items = [...companies, ...companies]
+
+export default function TrustCarousel() {
+ return (
+
+
Engineering background from
+
+
+ {items.map((co, i) => (
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+
+
{co.name}
+
+ ))}
+
+
+
+ )
+}
From 6c416f31e2c426a7ee2aeeaae792bb983f080cda Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:29:07 -0700
Subject: [PATCH 06/18] Redesign trust carousel: greywingsec-style stacked
logo+name cards
- Store all 8 company logos locally in public/logos/ as SVGs
(Google, Meta, Apple, Samsung, Bank of America from simple-icons;
Hulu, Gap, Avid as hand-crafted SVG wordmarks)
- Carousel cards: logo centered above, company name below
- CSS brightness/invert filter renders logos as white on dark bg
- Logo+name brighten on card hover; animation pauses on hover
- Edge fade mask via CSS mask-image for clean entry/exit
- Each card uniform width with border-right dividers
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
public/logos/apple.svg | 1 +
public/logos/avid.svg | 3 ++
public/logos/bankofamerica.svg | 1 +
public/logos/desktop.ini | 4 +++
public/logos/gap.svg | 3 ++
public/logos/google.svg | 1 +
public/logos/hulu.svg | 3 ++
public/logos/meta.svg | 1 +
public/logos/samsung.svg | 1 +
src/app/globals.css | 51 +++++++++++++++++++-------------
src/components/TrustCarousel.tsx | 30 +++++++++----------
11 files changed, 62 insertions(+), 37 deletions(-)
create mode 100644 public/logos/apple.svg
create mode 100644 public/logos/avid.svg
create mode 100644 public/logos/bankofamerica.svg
create mode 100644 public/logos/desktop.ini
create mode 100644 public/logos/gap.svg
create mode 100644 public/logos/google.svg
create mode 100644 public/logos/hulu.svg
create mode 100644 public/logos/meta.svg
create mode 100644 public/logos/samsung.svg
diff --git a/public/logos/apple.svg b/public/logos/apple.svg
new file mode 100644
index 0000000..915e890
--- /dev/null
+++ b/public/logos/apple.svg
@@ -0,0 +1 @@
+Apple
\ No newline at end of file
diff --git a/public/logos/avid.svg b/public/logos/avid.svg
new file mode 100644
index 0000000..ecdb902
--- /dev/null
+++ b/public/logos/avid.svg
@@ -0,0 +1,3 @@
+
+ avid
+
diff --git a/public/logos/bankofamerica.svg b/public/logos/bankofamerica.svg
new file mode 100644
index 0000000..123c151
--- /dev/null
+++ b/public/logos/bankofamerica.svg
@@ -0,0 +1 @@
+Bank of America
\ No newline at end of file
diff --git a/public/logos/desktop.ini b/public/logos/desktop.ini
new file mode 100644
index 0000000..ab17096
--- /dev/null
+++ b/public/logos/desktop.ini
@@ -0,0 +1,4 @@
+[ViewState]
+Mode=
+Vid=
+FolderType=Documents
diff --git a/public/logos/gap.svg b/public/logos/gap.svg
new file mode 100644
index 0000000..620a53c
--- /dev/null
+++ b/public/logos/gap.svg
@@ -0,0 +1,3 @@
+
+ GAP
+
diff --git a/public/logos/google.svg b/public/logos/google.svg
new file mode 100644
index 0000000..5e2ccf1
--- /dev/null
+++ b/public/logos/google.svg
@@ -0,0 +1 @@
+Google
\ No newline at end of file
diff --git a/public/logos/hulu.svg b/public/logos/hulu.svg
new file mode 100644
index 0000000..1ee7450
--- /dev/null
+++ b/public/logos/hulu.svg
@@ -0,0 +1,3 @@
+
+ hulu
+
diff --git a/public/logos/meta.svg b/public/logos/meta.svg
new file mode 100644
index 0000000..385fb0a
--- /dev/null
+++ b/public/logos/meta.svg
@@ -0,0 +1 @@
+Meta
\ No newline at end of file
diff --git a/public/logos/samsung.svg b/public/logos/samsung.svg
new file mode 100644
index 0000000..4a01bbb
--- /dev/null
+++ b/public/logos/samsung.svg
@@ -0,0 +1 @@
+Samsung
\ No newline at end of file
diff --git a/src/app/globals.css b/src/app/globals.css
index a8c1794..1f1da58 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -313,64 +313,73 @@ nav {
/* TRUST CAROUSEL */
.trust-bar {
- padding: 20px 0;
+ padding: 28px 0 32px;
border-bottom: 1px solid var(--border);
overflow: hidden;
}
-.trust-carousel-label {
+.trust-bar-label {
font-family: var(--mono);
font-size: 10px;
color: var(--text4);
letter-spacing: 0.15em;
text-transform: uppercase;
- margin-bottom: 16px;
+ margin-bottom: 20px;
}
-.trust-carousel-wrap {
+.trust-carousel-outer {
overflow: hidden;
- mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
- -webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
+ mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%);
+ -webkit-mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%);
}
.trust-carousel-track {
display: flex;
- align-items: center;
+ align-items: stretch;
gap: 0;
width: max-content;
- animation: trust-scroll 32s linear infinite;
+ animation: trust-scroll 36s linear infinite;
}
.trust-carousel-track:hover { animation-play-state: paused; }
-.trust-carousel-item {
+.trust-carousel-card {
display: flex;
+ flex-direction: column;
align-items: center;
- gap: 8px;
- padding: 6px 28px;
+ justify-content: center;
+ gap: 10px;
+ padding: 16px 32px;
border-right: 1px solid var(--border);
- white-space: nowrap;
+ min-width: 130px;
+ transition: background 0.2s;
+ cursor: default;
}
+.trust-carousel-card:hover { background: var(--bg2); }
-.trust-carousel-logo {
- width: 22px;
- height: 22px;
+.trust-carousel-img {
+ height: 36px;
+ width: auto;
+ max-width: 90px;
object-fit: contain;
filter: brightness(0) invert(1) opacity(0.35);
- transition: filter 0.2s;
- flex-shrink: 0;
+ transition: filter 0.25s;
+ display: block;
}
-.trust-carousel-item:hover .trust-carousel-logo {
+.trust-carousel-card:hover .trust-carousel-img {
filter: brightness(0) invert(1) opacity(0.75);
}
.trust-carousel-name {
font-family: var(--mono);
- font-size: 11px;
+ font-size: 10px;
color: var(--text3);
- letter-spacing: 0.06em;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ text-align: center;
+ white-space: nowrap;
transition: color 0.2s;
}
-.trust-carousel-item:hover .trust-carousel-name { color: var(--text2); }
+.trust-carousel-card:hover .trust-carousel-name { color: var(--text2); }
@keyframes trust-scroll {
from { transform: translateX(0); }
diff --git a/src/components/TrustCarousel.tsx b/src/components/TrustCarousel.tsx
index 77dbe83..c9be0b8 100644
--- a/src/components/TrustCarousel.tsx
+++ b/src/components/TrustCarousel.tsx
@@ -1,32 +1,30 @@
const companies = [
- { name: 'Google · Pixel', domain: 'google.com' },
- { name: 'Meta', domain: 'meta.com' },
- { name: 'Apple', domain: 'apple.com' },
- { name: 'Samsung', domain: 'samsung.com' },
- { name: 'Hulu', domain: 'hulu.com' },
- { name: 'Bank of America', domain: 'bankofamerica.com' },
- { name: 'Gap', domain: 'gap.com' },
- { name: 'Avid Technology', domain: 'avid.com' },
+ { name: 'Google · Pixel', logo: '/logos/google.svg' },
+ { name: 'Meta', logo: '/logos/meta.svg' },
+ { name: 'Apple', logo: '/logos/apple.svg' },
+ { name: 'Samsung', logo: '/logos/samsung.svg' },
+ { name: 'Hulu', logo: '/logos/hulu.svg' },
+ { name: 'Bank of America', logo: '/logos/bankofamerica.svg' },
+ { name: 'Gap', logo: '/logos/gap.svg' },
+ { name: 'Avid Technology', logo: '/logos/avid.svg' },
]
-// Duplicate for seamless infinite loop
+// Duplicate list so the scroll loops seamlessly
const items = [...companies, ...companies]
export default function TrustCarousel() {
return (
-
Engineering background from
-
+
Engineering background from
+
{items.map((co, i) => (
-
+
= companies.length}>
{/* eslint-disable-next-line @next/next/no-img-element */}
{co.name}
From c3ddf9ae809773f539b9b0061f1b39a7feef1a94 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:29:15 -0700
Subject: [PATCH 07/18] Remove stray Windows desktop.ini from logos dir
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
public/logos/desktop.ini | 4 ----
1 file changed, 4 deletions(-)
delete mode 100644 public/logos/desktop.ini
diff --git a/public/logos/desktop.ini b/public/logos/desktop.ini
deleted file mode 100644
index ab17096..0000000
--- a/public/logos/desktop.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[ViewState]
-Mode=
-Vid=
-FolderType=Documents
From c433967a6592c3f6dcffaac4ef1b7c3bf6195ff2 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:29:27 -0700
Subject: [PATCH 08/18] Ignore Windows desktop.ini and Thumbs.db
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
.gitignore | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.gitignore b/.gitignore
index fdc6e97..986fc8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,8 @@
/out/
/build
.DS_Store
+desktop.ini
+Thumbs.db
*.pem
npm-debug.log*
yarn-debug.log*
From e4545c18a231659fd018ad3646a51f4e9bee86fe Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:36:09 -0700
Subject: [PATCH 09/18] Center carousel within page max-width
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/app/globals.css | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/app/globals.css b/src/app/globals.css
index 1f1da58..9971cd4 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -329,8 +329,11 @@ nav {
.trust-carousel-outer {
overflow: hidden;
- mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%);
- -webkit-mask-image: linear-gradient(to right, transparent 0%, black 8%, black 92%, transparent 100%);
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 0 2.5rem;
+ mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
+ -webkit-mask-image: linear-gradient(to right, transparent 0%, black 10%, black 90%, transparent 100%);
}
.trust-carousel-track {
From 44207fbec7a90fe25eb169fd206157e3c1a02f8b Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:45:06 -0700
Subject: [PATCH 10/18] Fix fonts, update logos with grayscale-to-color hover,
rename Google
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Move next/font classNames to so CSS vars resolve in :root
- Download real SVGs: Google (Wikimedia multicolor), Meta, Apple, Samsung,
Hulu, Bank of America (worldvectorlogo), Avid (worldvectorlogo)
- Craft accurate Gap SVG (white GAP on navy #002868 — matches brand)
- Hover: filter transitions from grayscale+brightness to full color (0.35s)
- Rename 'Google · Pixel' to 'Google' in carousel
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
public/logos/apple.svg | 4 +++-
public/logos/avid.svg | 4 +---
public/logos/bankofamerica.svg | 2 +-
public/logos/gap.svg | 5 +++--
public/logos/google.svg | 3 ++-
public/logos/hulu.svg | 4 +---
public/logos/meta.svg | 26 +++++++++++++++++++++++++-
public/logos/samsung.svg | 2 +-
src/app/globals.css | 7 ++++---
src/app/layout.tsx | 6 ++----
src/components/TrustCarousel.tsx | 2 +-
11 files changed, 44 insertions(+), 21 deletions(-)
diff --git a/public/logos/apple.svg b/public/logos/apple.svg
index 915e890..82b0cdd 100644
--- a/public/logos/apple.svg
+++ b/public/logos/apple.svg
@@ -1 +1,3 @@
-
Apple
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/public/logos/avid.svg b/public/logos/avid.svg
index ecdb902..ead9202 100644
--- a/public/logos/avid.svg
+++ b/public/logos/avid.svg
@@ -1,3 +1 @@
-
- avid
-
+
\ No newline at end of file
diff --git a/public/logos/bankofamerica.svg b/public/logos/bankofamerica.svg
index 123c151..f2630d5 100644
--- a/public/logos/bankofamerica.svg
+++ b/public/logos/bankofamerica.svg
@@ -1 +1 @@
-
Bank of America
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/logos/gap.svg b/public/logos/gap.svg
index 620a53c..3b92bad 100644
--- a/public/logos/gap.svg
+++ b/public/logos/gap.svg
@@ -1,3 +1,4 @@
-
- GAP
+
+
+ GAP
diff --git a/public/logos/google.svg b/public/logos/google.svg
index 5e2ccf1..3790851 100644
--- a/public/logos/google.svg
+++ b/public/logos/google.svg
@@ -1 +1,2 @@
-Google
\ No newline at end of file
+
+
diff --git a/public/logos/hulu.svg b/public/logos/hulu.svg
index 1ee7450..16476f7 100644
--- a/public/logos/hulu.svg
+++ b/public/logos/hulu.svg
@@ -1,3 +1 @@
-
- hulu
-
+
\ No newline at end of file
diff --git a/public/logos/meta.svg b/public/logos/meta.svg
index 385fb0a..fbfca7b 100644
--- a/public/logos/meta.svg
+++ b/public/logos/meta.svg
@@ -1 +1,25 @@
-Meta
\ No newline at end of file
+
+
+Logo of Meta Platforms -- Graphic created by Detmar Owen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/logos/samsung.svg b/public/logos/samsung.svg
index 4a01bbb..627cbf2 100644
--- a/public/logos/samsung.svg
+++ b/public/logos/samsung.svg
@@ -1 +1 @@
-Samsung
\ No newline at end of file
+Samsung
\ No newline at end of file
diff --git a/src/app/globals.css b/src/app/globals.css
index 9971cd4..92a51c1 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -364,12 +364,13 @@ nav {
width: auto;
max-width: 90px;
object-fit: contain;
- filter: brightness(0) invert(1) opacity(0.35);
- transition: filter 0.25s;
+ filter: grayscale(1) brightness(1.9) opacity(0.45);
+ transition: filter 0.35s ease, opacity 0.35s ease;
display: block;
}
.trust-carousel-card:hover .trust-carousel-img {
- filter: brightness(0) invert(1) opacity(0.75);
+ filter: none;
+ opacity: 0.9;
}
.trust-carousel-name {
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 8741d0b..12805f5 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -48,10 +48,8 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
-
-
- {children}
-
+
+ {children}
)
}
diff --git a/src/components/TrustCarousel.tsx b/src/components/TrustCarousel.tsx
index c9be0b8..e0ee1c6 100644
--- a/src/components/TrustCarousel.tsx
+++ b/src/components/TrustCarousel.tsx
@@ -1,5 +1,5 @@
const companies = [
- { name: 'Google · Pixel', logo: '/logos/google.svg' },
+ { name: 'Google', logo: '/logos/google.svg' },
{ name: 'Meta', logo: '/logos/meta.svg' },
{ name: 'Apple', logo: '/logos/apple.svg' },
{ name: 'Samsung', logo: '/logos/samsung.svg' },
From 281f35fdc3c089dfc9b996d118127101611e3461 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:53:14 -0700
Subject: [PATCH 11/18] Carousel: correct logos, purple Avid, bigger logos,
remove names
- Download correct Gap logo from Wikimedia (commons/6/69/Gap_logo.svg)
- Replace Avid worldvectorlogo gray (#7a7d82) with brand purple (#7B2FBE)
- Increase logo height 36px -> 44px, max-width 90px -> 110px (helps Samsung)
- Remove company name labels from carousel cards (logo-only layout)
- Tighten card padding now that names are gone
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
public/logos/avid.svg | 2 +-
public/logos/gap.svg | 31 ++++++++++++++++++++++++++++---
src/app/globals.css | 24 +++++-------------------
src/components/TrustCarousel.tsx | 1 -
4 files changed, 34 insertions(+), 24 deletions(-)
diff --git a/public/logos/avid.svg b/public/logos/avid.svg
index ead9202..4696626 100644
--- a/public/logos/avid.svg
+++ b/public/logos/avid.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/logos/gap.svg b/public/logos/gap.svg
index 3b92bad..1249ab4 100644
--- a/public/logos/gap.svg
+++ b/public/logos/gap.svg
@@ -1,4 +1,29 @@
-
-
- GAP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/globals.css b/src/app/globals.css
index 92a51c1..f2f1d96 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -347,25 +347,23 @@ nav {
.trust-carousel-card {
display: flex;
- flex-direction: column;
align-items: center;
justify-content: center;
- gap: 10px;
- padding: 16px 32px;
+ padding: 14px 36px;
border-right: 1px solid var(--border);
- min-width: 130px;
+ min-width: 120px;
transition: background 0.2s;
cursor: default;
}
.trust-carousel-card:hover { background: var(--bg2); }
.trust-carousel-img {
- height: 36px;
+ height: 44px;
width: auto;
- max-width: 90px;
+ max-width: 110px;
object-fit: contain;
filter: grayscale(1) brightness(1.9) opacity(0.45);
- transition: filter 0.35s ease, opacity 0.35s ease;
+ transition: filter 0.35s ease;
display: block;
}
.trust-carousel-card:hover .trust-carousel-img {
@@ -373,18 +371,6 @@ nav {
opacity: 0.9;
}
-.trust-carousel-name {
- font-family: var(--mono);
- font-size: 10px;
- color: var(--text3);
- letter-spacing: 0.08em;
- text-transform: uppercase;
- text-align: center;
- white-space: nowrap;
- transition: color 0.2s;
-}
-.trust-carousel-card:hover .trust-carousel-name { color: var(--text2); }
-
@keyframes trust-scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
diff --git a/src/components/TrustCarousel.tsx b/src/components/TrustCarousel.tsx
index e0ee1c6..bc448af 100644
--- a/src/components/TrustCarousel.tsx
+++ b/src/components/TrustCarousel.tsx
@@ -26,7 +26,6 @@ export default function TrustCarousel() {
alt={co.name}
className="trust-carousel-img"
/>
- {co.name}
))}
From 0dbe2c52d00aa6a4986136288ab622223bc5af7b Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:58:07 -0700
Subject: [PATCH 12/18] Replace Avid logo with correct current branding (PNG)
- Download actual Avid logo image matching current purple brand identity
- Switch carousel from avid.svg to avid.png
- Remove outdated avid.svg and test file
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
public/logos/avid.png | Bin 0 -> 3080 bytes
public/logos/avid.svg | 1 -
public/logos/avid_v2.svg | 70 +++++++++++++++++++++++++++++++
src/components/TrustCarousel.tsx | 2 +-
4 files changed, 71 insertions(+), 2 deletions(-)
create mode 100644 public/logos/avid.png
delete mode 100644 public/logos/avid.svg
create mode 100644 public/logos/avid_v2.svg
diff --git a/public/logos/avid.png b/public/logos/avid.png
new file mode 100644
index 0000000000000000000000000000000000000000..47121d3ba41491b920df6d472140ce34e3557d26
GIT binary patch
literal 3080
zcmV+j4EOViP)
PU&t7<#i(Fa1`Zp9Oh^M|Mc$t(7X1-sq@{=
z_}R$!?dAKbgYSMd=h40Pfjj8Qvh@1*{*hVgu8HrWcI}g0>y=^adoJg_q4L|y_~qI9
zvXJneZ|u03@q|L?j#TQJY3#*xj!S<$o?ube07}m^i000X!Nkl
zX`w(R?}4{LyPaP&QcZf!6%WscxI()YDw$X_Q2nQtNB`cgBz#m3y^mWu_Ly5Po<#
z%Y$KPzv8s}qkhx1CjW^($z=c8H2&rE;xuxia#6K1Qa^~ldhe^NY&y$XyD?|_pGb~9
z%&GeBWr>=fySzGDE>zytzKt=_qyJ`pUO7YYTdOT62l`n@G)Erfw(;|d)D1aAxX6J@
zePqhuz`E5_k5t;@%yZ27)IIzlQHyAnUw8^H4pd(1Z#(ad6?@(GH-g2N&8D0v{bx2k
zKLMWuh>yO)i#?U|#uG+=&_Ddi`F;9P-Wp>jyrw8HWIO;!J%ty0DvPN3BhKCaqsif`8L%UxB3)?Awt65!~@Fk&fmeyjWA|SQc1}
zJUsl-Q)fxC)^cUa3M(6>&|_d!JcJipD)*fxCNb*m-SyZ9EZH!AoDwv}Xfv7>Sf
zK3;Z*{XNvRd@K6uG{a+GDI^zO?5H3n3hE#MVaU6Hb**PAa^b~_3O1?Aexn~rLU)#I
z*=>YxDq^b_D=IhG0tEM6eufVC?PYx-|5UceJXIhROrF{^%^Gzx5}=KIBCyv^)<%
zMI^k~P+8zF824Qu7s;c*`Cu3-0^!Ai3N;uJz6(0+`4GHz3Pi;{yjV~nH;#tkyCgA~
z@4%-5ecZ_Vb_*}&ROm@=lA?_Xxch~S>;b8`g%@Kg*C{>6C484)odp|5gHkEN%Y*6L
ztz=9^Yo6e~OR!BzY|asu$|k(zmf^*eivGb;)OWcKGz~-dMPZ`|kPsE?@M21Zk+6dM
zF7IJ{7j!Kfp33G&sw~2bAr;OjOSeV@=HSSgF}Vn~Ix=)irKs}N%t-dFpf
zVi8`n3Vc%4W@bbyq+Bcvw)p#v+;l9hU*Uo~Z2=D%P&s2)l%RT2+8&EMyRKa8J
zg-_o_k>}3(r?TnHt4``oS9~gF<|3Z&a_uqSh1d%%Gz2P}@baP!FMKL@W&r`(H!sCH
zLt-a5nSQT9P~n6ZHWkxND;+bQqtg?@&CP|=4MHkIF|bEh7B7u;(pH5e+z5ppWL
zaH;UTmb!r4#hvDUI!pGXd58f~p~DN8ibdj(y2QTBlkburkBABxUYJxaE#6~j--Ys8
zOsPPNU}-AE-V2io<3My*xFP4mcOg%g50eUNj`672C2u47KZQK_F7dneF$PDas89l(
zdtp(rJ?Mb@E;I4SLVGQPr2?OOVNtO@6oluyi2E+I*D_!#n;&o59_l$%><`d^IoMlC
z=ewX~K1M*Ln>ogzV(7Kh70h?ZIa^9YrBZ~KWqn%DpyHAsOw@M~H7TPb29yC)*}N%r
z`~6QDRE)irx^lEO9e)qa>EDcnN)cYJ_*BfjmbwVvg}vXbm)2_;1C>p9`7`5EaXh5e
zJzu@khg`r{I7f1C505@+IZFq!%nG!>6)|`j0FsR$fb8ow^-+xs8oA
z>AjY*P^oIq=%~2I4`4L>%FXLH6@+=+>KYQi<|c
z)()|Fv0Hqwv0kgDpA{@2(_`-QmTI{%sRRqD)Q$@o5G8A^7Ax_;}@j*u7bcW
z3fiIK{UTI>%fHOMmQp=eIOG@5ZBy}G7gFN#FX9_$jkX%7_Zq4Kmw#D!Ev4Kl=Q>ew
zs+=33-mvrKUp8J#sotnKR+;wZwbVsi{>2$T@zM%9^+d(BB72ZJ_Kuf-;blm)>W7N+
z?FHUy4>pC5v3lQyD3z>Lu-n#Za=+=rtJhMK%jI7#mobK~lP_>9j?bF5P1#@mMT~xW
z@n^=+r+d0VD%ibS4wrx7WfK_+OGW0kHFlc?gwJlLit4b$)h-MLr6P1QVZi$wsg)q<
zyWlHOjsiw|sarfl-hE1Ds1W%s7I`gojqt|XmylhB`qfYHccn$^{l8QuWBXi-I
zcF^Tt#E8e08-r}Nt8
zwbbRUuZRVdN?R%pWu)DNAIj@oy6)z+RN~$Z3#LQkROA9mrGiRdU4^_drIqhuo!8P3
zvj$289)H59h^$bWDyZ}^0V_omCu}XCtn*qLVjhc4!D$FgB~s8;9gZrfcsT^glfv(|
zYM2eKUP~orwi}#C_yCN5(gCGXK}ED+ic@uAW2}bVqy_;ve;o!GUHdFr9QHt^Y!K;
z%1c10R8Z+{Vyp?`)1&Qfag-Ys0i!it>Bg`OP3^2NcRU4@N(B`^r|UV&H034UR=LWp
za}q$!Qu;TTt?*H4vVT#&0!pRrD)P0bsd1vaqIUBV&!mTm(>e9D@fQ^;-tsg`qAd0n
zP%4!^*pf^v@ue&m?FDSz=2&-0bIlgc@%n4N_&f2-Wbfo(Wh%|0fXdQXJqb;BjWxSd
z-N(&R6@T*Xaq+JG4)NLZ>&sC%-CRuiVp8b(W4&y(Xt_Rj-BlBlOM>V>`O-fsL)3p4
W8_jja=sN}g0000
\ No newline at end of file
diff --git a/public/logos/avid_v2.svg b/public/logos/avid_v2.svg
new file mode 100644
index 0000000..4b69bca
--- /dev/null
+++ b/public/logos/avid_v2.svg
@@ -0,0 +1,70 @@
+
+
+
+
+
+ 404 - Page Not Found - Vector Logo Zone
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 404 - Page Not Found
+
+
+
+
The page you requested could not be found!
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/TrustCarousel.tsx b/src/components/TrustCarousel.tsx
index bc448af..3a6bd6d 100644
--- a/src/components/TrustCarousel.tsx
+++ b/src/components/TrustCarousel.tsx
@@ -6,7 +6,7 @@ const companies = [
{ name: 'Hulu', logo: '/logos/hulu.svg' },
{ name: 'Bank of America', logo: '/logos/bankofamerica.svg' },
{ name: 'Gap', logo: '/logos/gap.svg' },
- { name: 'Avid Technology', logo: '/logos/avid.svg' },
+ { name: 'Avid Technology', logo: '/logos/avid.png' },
]
// Duplicate list so the scroll loops seamlessly
From 336efc88c93074b59a751293448b292941eae161 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 01:58:18 -0700
Subject: [PATCH 13/18] Remove stray avid_v2.svg test file
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
public/logos/avid_v2.svg | 70 ----------------------------------------
1 file changed, 70 deletions(-)
delete mode 100644 public/logos/avid_v2.svg
diff --git a/public/logos/avid_v2.svg b/public/logos/avid_v2.svg
deleted file mode 100644
index 4b69bca..0000000
--- a/public/logos/avid_v2.svg
+++ /dev/null
@@ -1,70 +0,0 @@
-
-
-
-
-
- 404 - Page Not Found - Vector Logo Zone
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 404 - Page Not Found
-
-
-
-
The page you requested could not be found!
-
-
-
-
-
-
-
-
\ No newline at end of file
From a68e495597b3b4ff730b2aeeea0147d5eb689802 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 07:32:25 -0700
Subject: [PATCH 14/18] Full mobile-responsive overhaul for all device sizes
- Add Viewport export (device-width, initialScale 1, maximumScale 5)
- Fix nav-inner padding not reducing on small screens (was stuck at 2.5rem)
- Fix mobile-menu padding to scale down on small screens
- Add overflow-x: auto to .t-body so terminal scrolls on narrow screens
- Fix case-stats: row at 900px, column at 560px (was never returning to col)
- Stack footer links vertically on mobile
- iOS Safari zoom fix: form inputs/selects/textarea set to font-size 16px
- Full-width hero + CTA buttons on mobile for easier tap targets
- Reduce card padding (for-card, svc, plan, ai-banner, case) at 560px
- Scale carousel cards down at 560px and 400px
- Add 400px breakpoint for iPhone SE / Galaxy S series:
h1 28px, tighter carousel, smaller terminal font, smaller section titles
- Reduce all intra-section padding progressively across breakpoints
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/app/globals.css | 60 +++++++++++++++++++++++++++++++++++++++------
src/app/layout.tsx | 8 +++++-
2 files changed, 59 insertions(+), 9 deletions(-)
diff --git a/src/app/globals.css b/src/app/globals.css
index f2f1d96..54a9184 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -290,7 +290,13 @@ nav {
.t-title { font-size: 11px; color: var(--text3); margin: 0 auto; }
-.t-body { padding: 18px; font-size: 12px; line-height: 1.9; }
+.t-body {
+ padding: 18px;
+ font-size: 12px;
+ line-height: 1.9;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+}
.tc { color: #3a4f66; }
.tk { color: #6e9fd4; }
@@ -1019,6 +1025,8 @@ footer { border-top: 1px solid var(--border); padding: 2rem 0; }
@media (max-width: 900px) {
.nav-links, .nav-cta { display: none; }
.nav-hamburger { display: flex; }
+ .nav-inner { padding: 0 1.5rem; }
+ .mobile-menu { padding: 1.5rem; }
.hero-layout { grid-template-columns: 1fr; gap: 2.5rem; }
.hero { padding: 80px 0 60px; }
.hero h1 { font-size: 42px; }
@@ -1026,26 +1034,62 @@ footer { border-top: 1px solid var(--border); padding: 2rem 0; }
.svc-grid { grid-template-columns: 1fr; }
.process-steps { grid-template-columns: 1fr 1fr; gap: 2rem; }
.case { grid-template-columns: 1fr; }
- .case-stats { flex-direction: row; gap: 2rem; }
+ .case-stats { flex-direction: row; gap: 2rem; flex-wrap: wrap; }
.pricing-grid { grid-template-columns: 1fr; }
.ai-banner { grid-template-columns: 1fr; }
section { padding: 64px 0; }
.cta-section { padding: 80px 0; }
+ .trust-carousel-outer { padding: 0 1.5rem; }
+ .footer-inner { flex-direction: column; align-items: flex-start; gap: 1.25rem; }
}
@media (max-width: 720px) {
.form-grid { grid-template-columns: 1fr; }
}
+/* Prevent iOS Safari from auto-zooming inputs (requires font-size >= 16px) */
+@media (max-width: 768px) {
+ .form-input, .form-select, .form-textarea { font-size: 16px; }
+}
+
@media (max-width: 560px) {
.wrap { padding: 0 1.25rem; }
- .hero { padding: 60px 0 50px; }
+ .nav-inner { padding: 0 1.25rem; }
+ .mobile-menu { padding: 1.25rem; }
+ .hero { padding: 60px 0 48px; }
.hero h1 { font-size: 34px; }
+ .hero-sub { font-size: 15px; }
.hero-btns { flex-direction: column; }
- .btn-primary, .btn-ghost { text-align: center; }
- .hero-stats { grid-template-columns: repeat(2, 1fr); }
+ .hero-btns .btn-primary,
+ .hero-btns .btn-ghost { text-align: center; width: 100%; }
.process-steps { grid-template-columns: 1fr; }
- .cta-row { flex-direction: column; align-items: center; }
- .ai-banner { padding: 1.75rem; }
- .case { padding: 1.75rem; }
+ .case { padding: 1.5rem; }
+ .case-stats { flex-direction: column; gap: 1rem; }
+ .for-card { padding: 1.5rem; }
+ .svc { padding: 1.5rem; }
+ .plan { padding: 1.5rem; }
+ .ai-banner { padding: 1.5rem; }
+ .ai-banner h3 { font-size: 20px; }
+ .cta-row { flex-direction: column; align-items: stretch; }
+ .cta-row .btn-primary,
+ .cta-row .btn-ghost { text-align: center; }
+ .trust-carousel-outer { padding: 0 1rem; }
+ .trust-carousel-card { padding: 12px 20px; min-width: 90px; }
+ .trust-carousel-img { height: 34px; max-width: 80px; }
+ .t-body { font-size: 10.5px; padding: 14px; }
+ .contact-form { padding: 1.25rem; }
+}
+
+/* Very small phones: iPhone SE (375px), Galaxy S series (360px) */
+@media (max-width: 400px) {
+ .wrap { padding: 0 1rem; }
+ .nav-inner { padding: 0 1rem; }
+ .mobile-menu { padding: 1rem; }
+ .hero h1 { font-size: 28px; }
+ .hero-kicker { font-size: 9px; letter-spacing: 0.12em; }
+ .trust-carousel-card { padding: 10px 14px; min-width: 76px; }
+ .trust-carousel-img { height: 28px; max-width: 64px; }
+ .t-body { font-size: 9.5px; padding: 10px; }
+ .section-title { font-size: 24px; }
+ .cs-num { font-size: 22px; }
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 12805f5..b4fd286 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,4 +1,4 @@
-import type { Metadata } from 'next'
+import type { Metadata, Viewport } from 'next'
import { DM_Mono, Instrument_Serif, DM_Sans } from 'next/font/google'
import './globals.css'
@@ -25,6 +25,12 @@ const dmSans = DM_Sans({
display: 'swap',
})
+export const viewport: Viewport = {
+ width: 'device-width',
+ initialScale: 1,
+ maximumScale: 5,
+}
+
export const metadata: Metadata = {
metadataBase: new URL('https://testopseng.com'),
title: 'TestOps Engineering — QA & Automation Consulting',
From 7f0b08348891c03f0e4e9750a3ba851824fe78b2 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 07:45:58 -0700
Subject: [PATCH 15/18] Fix mobile bottom gap: move overflow-x to html, set bg
on html element
- overflow-x: hidden on body is a known iOS Safari bug that creates gaps
- Replaced with overflow-x: clip on html (no scroll container side-effects)
- Added background: var(--bg) to html so overscroll area matches page color
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/app/globals.css | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/app/globals.css b/src/app/globals.css
index 54a9184..eece0f5 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -20,7 +20,11 @@
--body: var(--font-body), sans-serif;
}
-html { scroll-behavior: smooth; }
+html {
+ scroll-behavior: smooth;
+ background: var(--bg);
+ overflow-x: clip;
+}
body {
background: var(--bg);
@@ -28,7 +32,6 @@ body {
font-family: var(--body);
font-size: 15px;
line-height: 1.7;
- overflow-x: hidden;
}
/* LAYOUT */
From 97067e22454cf103a2cb7d869e4f6a571ff2537e Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 21:49:39 -0700
Subject: [PATCH 16/18] Fix contact page nav: section links route home, CTA
becomes Home button
- Use usePathname() to detect current page
- On non-home pages, prefix #section hrefs with '/' (e.g. /#for)
so clicking them navigates home and scrolls to the right section
- On contact page, swap 'Contact' CTA to 'Home' so users can always escape
- Scroll highlight and scroll listener only run on home page
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/components/Nav.tsx | 37 +++++++++++++++++++++++++------------
1 file changed, 25 insertions(+), 12 deletions(-)
diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx
index 30642e8..910bd75 100644
--- a/src/components/Nav.tsx
+++ b/src/components/Nav.tsx
@@ -1,14 +1,21 @@
'use client'
import { useState, useEffect } from 'react'
+import { usePathname } from 'next/navigation'
import Image from 'next/image'
import Link from 'next/link'
export default function Nav() {
+ const pathname = usePathname()
+ const isHome = pathname === '/' || pathname === ''
const [menuOpen, setMenuOpen] = useState(false)
const [activeSection, setActiveSection] = useState('')
+ // On non-home pages, prefix section links with '/' so they navigate home first
+ const s = (hash: string) => isHome ? hash : `/${hash}`
+
useEffect(() => {
+ if (!isHome) return
const handleScroll = () => {
const scrollY = window.pageYOffset
const sections = document.querySelectorAll('section[id], div[id]')
@@ -23,7 +30,7 @@ export default function Nav() {
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
- }, [])
+ }, [isHome])
useEffect(() => {
const handleOutsideClick = (e: MouseEvent) => {
@@ -44,7 +51,7 @@ export default function Nav() {
}, [menuOpen])
const navColor = (id: string) =>
- activeSection === id ? 'var(--accent)' : undefined
+ isHome && activeSection === id ? 'var(--accent)' : undefined
return (
@@ -55,12 +62,15 @@ export default function Nav() {
-
Contact
+ {isHome
+ ?
Contact
+ :
Home
+ }
setMenuOpen((o) => !o)}
@@ -72,11 +82,14 @@ export default function Nav() {
)
From 9a48fda49cc5434eca1f497b21bb333e1abe3966 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Tue, 21 Jul 2026 22:26:34 -0700
Subject: [PATCH 17/18] Remove placeholder metrics note from case studies
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/app/page.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/app/page.tsx b/src/app/page.tsx
index d87d676..c7bf60b 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -251,7 +251,6 @@ export default function Home() {
CI/CD
Mobile hardware
-
* Replace these metrics with your real numbers before going live
−86% Flakiness rate
@@ -276,7 +275,6 @@ export default function Home() {
Python
API testing
-
* Replace these metrics with your real numbers before going live
+61% API coverage
From 51640f08f5478e702910b6ea521c131a462e5924 Mon Sep 17 00:00:00 2001
From: diantekyrie <48487406+diantekyrie@users.noreply.github.com>
Date: Wed, 22 Jul 2026 23:41:46 -0700
Subject: [PATCH 18/18] Add sitemap.xml and robots.txt to fix Search Console
canonical issue
- sitemap.ts generates /sitemap.xml with canonical URLs for / and /contact
- robots.ts generates /robots.txt pointing crawlers to the sitemap
- Both reference testopseng.com as the authoritative domain
- Helps Google resolve 'Alternate page with proper canonical tag' by
clearly declaring which URLs should be indexed
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
---
src/app/robots.ts | 11 +++++++++++
src/app/sitemap.ts | 19 +++++++++++++++++++
2 files changed, 30 insertions(+)
create mode 100644 src/app/robots.ts
create mode 100644 src/app/sitemap.ts
diff --git a/src/app/robots.ts b/src/app/robots.ts
new file mode 100644
index 0000000..c4f3e62
--- /dev/null
+++ b/src/app/robots.ts
@@ -0,0 +1,11 @@
+import { MetadataRoute } from 'next'
+
+export default function robots(): MetadataRoute.Robots {
+ return {
+ rules: {
+ userAgent: '*',
+ allow: '/',
+ },
+ sitemap: 'https://testopseng.com/sitemap.xml',
+ }
+}
diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts
new file mode 100644
index 0000000..de3d352
--- /dev/null
+++ b/src/app/sitemap.ts
@@ -0,0 +1,19 @@
+import { MetadataRoute } from 'next'
+
+export default function sitemap(): MetadataRoute.Sitemap {
+ const base = 'https://testopseng.com'
+ return [
+ {
+ url: base,
+ lastModified: new Date(),
+ changeFrequency: 'monthly',
+ priority: 1,
+ },
+ {
+ url: `${base}/contact`,
+ lastModified: new Date(),
+ changeFrequency: 'monthly',
+ priority: 0.8,
+ },
+ ]
+}