A professional project scope & brief clarity tool for agencies, freelancers, project managers, and delivery teams.
Live app: scopeseal.codezela.com Chrome extension: Install from Chrome Web Store Publisher: Codezela Technologies · Contact: info@codezela.com
ScopeSeal reviews messy client briefs, scope sections, project messages, and proposal drafts, then highlights missing details and risky wording before the work begins — so you don't discover the gaps mid-project when they cost real money.
Paste a client's brief, pick a project type, and ScopeSeal runs a deterministic analysis engine that scores the scope 0–100 across 9 weighted categories, flags 15+ missing items, detects 17 risky phrases (with negation awareness), and generates 4 copy-ready outputs you can paste straight into your internal notes, client reply, proposal, or a rewritten scope.
It ships as:
- A full web app (this repo) — deeper analysis, saved reports, templates, sharing, export, an admin dashboard, and an optional AI enhancement layer.
- A Chrome extension (
codezelat/scopeseal-chrome-ext) — quick capture of selected text from any page and a lightweight review, on explicit action only (no auto-upload, no background scanning).
Important: ScopeSeal is not legal advice. It flags scope-clarity risks and missing information — it does not evaluate contract validity or provide legal guidance. Risky wording is always framed as "possible risk," never as "wrong" or "invalid."
- What is ScopeSeal?
- Key Features
- How It Works
- The Analysis Engine
- Tech Stack
- Project Structure
- Architecture
- Data Model
- Authentication & Security
- Pages Reference
- API Reference
- Brand & Design System
- Animation Inventory
- Testing
- Quick Start
- Environment Variables
- Scripts
- Documentation
- Chrome Extension
- License
Every brief gets a single, weighted score with a band:
| Band | Score | Meaning |
|---|---|---|
| Clear | ≥ 70 | Scope is well-defined — low scope-creep risk |
| Needs Review | 40–69 | Partial coverage — clarify before starting |
| Risky | < 40 | Major gaps — likely to cause disputes or unpaid work |
The score is rendered with a signature animated seal-stamp ring that draws, rotates, and stamps down to reveal the number — a precision-lens × wax-seal metaphor that runs throughout the product.
Each category is scored independently with a weight that shifts based on the selected project type:
| Category | What it checks |
|---|---|
| Deliverables | Pages, screens, posts, features, design files, quantities |
| Timeline | Duration, deadlines, milestones, start/launch dates |
| Revisions | Revision rounds, feedback cycles, iteration limits |
| Payment | Milestones, deposits, retainers, currency, payment terms |
| Client Responsibility | Content, copy, brand assets, communication channels |
| Technical Responsibility | Hosting, domain, SSL, CDN, APIs, third-party tools, stack |
| Acceptance | Sign-off, UAT, go-live criteria, testing |
| Maintenance / Support | Warranty, bug fixes, support period, retainer terms |
| Exclusions | Out-of-scope items, change-request process |
The same brief scores differently depending on the project type — a website weights technical responsibility higher, while a maintenance contract weights support and exclusions more heavily:
| Project Type | Key Weight Shifts |
|---|---|
| Website | Deliverables, Technical, Acceptance, Revisions ↑ |
| SEO | Deliverables, Client, Timeline, Maintenance ↑ |
| Social Media Marketing | Deliverables, Client, Revisions ↑ |
| Branding | Deliverables, Client, Revisions, Acceptance ↑ |
| Custom Software | Technical, Acceptance, Timeline, Exclusions ↑ |
| Mobile App | Technical, Acceptance, Timeline, Exclusions ↑ |
| Maintenance / Support | Maintenance, Exclusions, Technical ↑ |
| General Service | Even weights across all categories |
The engine scans the brief for 15 critical scope elements and reports which are missing, with severity that escalates based on project-type weights:
- Final deliverables & source files
- Timeline / deadline
- Quantified deliverables (page/screen/post counts)
- Revision limits
- Payment milestones
- Content responsibility (who writes the copy?)
- Brand/assets responsibility (who provides the logo?)
- Hosting/domain responsibility
- Third-party tool/subscription responsibility
- Support period
- Maintenance terms
- Acceptance criteria
- Out-of-scope items
- Change-request process
- Communication channel
Each missing item includes practical guidance on what to add.
The engine flags language that signals loose scope — and it's smart about negation. "Unlimited revisions" is flagged, but "not unlimited" is not.
| Phrase | Severity |
|---|---|
unlimited |
High |
everything included |
High |
simple |
Medium |
quick |
Medium |
small change |
Medium |
basic website |
Medium |
as needed |
Medium |
ongoing support |
Medium |
make it like |
Medium |
same as the competitor |
Medium |
add later |
Medium |
we can decide after |
Medium |
asap |
Medium |
minor edit |
Low |
final changes |
Low |
just one page |
Low |
Each flagged phrase includes an occurrence count, a context snippet, and guidance.
Instead of just telling you what's wrong, ScopeSeal generates text you can use immediately:
- Internal Risk Summary — a one-paragraph briefing for your team with the weakest areas, high-severity missing items, and top risky phrases.
- Client-Friendly Note — a polite, professional message you can send to the client requesting clarification without sounding adversarial.
- Proposal Additional Info — a structured Markdown block with clarifications needed and wording to reconsider, ready to append to a proposal.
- Rewritten Scope — a template scope document with placeholder markers
(
___) exactly where the missing items should go.
The engine detects potentially sensitive data in the submitted text — credit card numbers, Social Security numbers, email/password combinations, and NDA / confidentiality mentions — and displays a warning so you don't accidentally share something you shouldn't.
An admin-configurable, OpenAI-compatible AI layer can rewrite a vague scope into a clearer version and list specific improvements. It's gated behind admin configuration (disabled by default), API keys are AES-256-GCM encrypted at rest, and it never replaces the deterministic engine — it's an optional enhancement on top of it.
- Guest mode — analyze up to 3 briefs without an account (cookie-tracked, rate-limited). Sign in for unlimited analyses.
- User accounts — email/password auth, saved reviews, templates, settings, password changes, account deletion.
- Admin role — full admin dashboard with platform analytics (user counts, review counts, average scores, band distribution, recent activity), user management (promote/demote), global settings (guest quota, site name, maintenance mode), and AI provider configuration.
- Shareable report links — every analysis gets an unguessable 12-char slug;
reports are shareable via
/result/{slug}withnoindexso they don't appear in search engines. - Markdown export — download any report as a
.mdfile with the full breakdown. - Copy to clipboard — one-click copy of the report, any of the 4 outputs, or the share link.
┌─────────────────────────────────────────────────────────────────────┐
│ ScopeSeal Flow │
└─────────────────────────────────────────────────────────────────────┘
Client brief / scope text
│
▼
┌──────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Rate limit │─────▶│ Guest quota / │─────▶│ Zod validation │
│ (per-IP) │ │ auth check │ │ (50–50k chars) │
└──────────────┘ └──────────────────┘ └────────┬────────┘
│
▼
┌────────────────────────────┐
│ DETERMINISTIC ENGINE │
│ (pure TypeScript) │
│ │
│ 1. Detect risky phrases │
│ (negation-aware) │
│ 2. Score 9 categories │
│ (weighted by type) │
│ 3. Detect missing items │
│ 4. Build 4 outputs │
│ 5. Compute overall score │
│ 6. Sensitive content scan │
└─────────────┬──────────────┘
│
┌────────────────┼────────────────┐
▼ ▼
┌────────────────────┐ ┌──────────────────┐
│ Persist Review │ │ Optional AI │
│ (Prisma → Neon) │ │ Enhancement │
│ + share slug │ │ (admin-gated, │
└─────────┬──────────┘ │ AES-encrypted) │
│ └──────────────────┘
▼
┌────────────────────┐
│ Result view │
│ Score ring + │
│ categories + │
│ missing + risks + │
│ outputs + export │
└────────────────────┘
The analysis engine is 100% deterministic and pure — given the same input, it always produces byte-identical output. It has zero database or server dependencies and is shared as a type contract across the web app and the Chrome extension.
The engine lives in src/lib/engine/ and is the heart of
the product.
| File | Responsibility |
|---|---|
types.ts |
Frozen API contract — AnalysisResult, ProjectType, CategoryId, Band, MissingItem, RiskHit, Outputs |
index.ts |
Main analyze() orchestrator + re-exports |
categories.ts |
9 category definitions with signal groups (pattern arrays) |
project-types.ts |
8 project types with per-type weight overrides |
scoring.ts |
Category scoring (primary + secondary signal bonus, risk penalty), weighted overall score, band calculation |
missing-items.ts |
15+ missing-item detectors with severity escalation |
risk-detector.ts |
17 risky-phrase detector with negation awareness |
suggestions.ts |
Practical, specific recommended actions |
outputs.ts |
4 copy-ready output builders (internal, client, proposal, rewritten scope) |
sensitive.ts |
Sensitive content warning detector (cards, SSNs, credentials, NDAs) |
text-utils.ts |
Tokenizer, word counter, sentence splitter, phrase finder with negation |
Each category is scored on a 0–100 scale:
- Signal detection — the engine searches the text for pattern groups
(keywords, phrases). Each category has a
primarysignal group and severalsecondarygroups. - Base score — if a primary signal is found, the category starts at 78
and earns a bonus (up to +18) for each additional secondary signal found, with
diminishing returns (
1.0, 0.6, 0.4, 0.3, 0.2). If only secondary signals are found, the score is proportional to coverage (found / total × 60). If nothing is found, the score is 0. - Risk penalty — if risky phrases map to this category, a penalty of up to
−12 is applied (
min(12, riskCount × 3)). - Weighted overall — the final 0–100 score is the weight-adjusted average across all 9 categories, where weights come from the selected project type.
The engine is fully deterministic — the same input always yields the same output.
This is enforced by a unit test that runs analyze() twice on multiple inputs and
asserts deep equality.
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router, Turbopack, React 19.2) | 16.2.9 |
| Language | TypeScript (strict mode) | ^5 |
| React | React + React DOM | 19.2.4 |
| Styling | Tailwind CSS v4 (CSS-first config, no tailwind.config.ts) |
^4 |
| Components | shadcn/ui (radix-nova style, brand-customized) | ^4.11.0 |
| UI Primitives | Radix UI | ^1.6.0 |
| Animation | Motion (motion/react, formerly Framer Motion) |
^12.40.0 |
| Database | PostgreSQL (NeonDB) via Prisma 7 | ^7.8.0 |
| DB Driver | @prisma/adapter-pg + pg (Prisma 7 driver adapter — required) |
^7.8.0 / ^8.21.0 |
| Auth | Auth.js v5 (next-auth@beta) + @auth/prisma-adapter |
5.0.0-beta.31 / ^2.11.2 |
| Validation | Zod | ^4.4.3 |
| Icons | lucide-react | ^1.20.0 |
| Password Hashing | bcryptjs (cost factor 12) | ^3.0.3 |
| Theming | next-themes (dark mode first-class) | ^0.4.6 |
| Toasts | sonner | ^2.0.7 |
| Unit Testing | Vitest | ^4.1.9 |
| E2E Testing | Playwright | ^1.61.0 |
| Linting | ESLint 9 + eslint-config-next | ^9 / 16.2.9 |
| Script Runner | tsx (for Prisma seed scripts) | ^4.22.4 |
| Deployment | Vercel | — |
| Package Manager | pnpm | — |
scopeseal/
├── src/
│ ├── app/
│ │ ├── (public)/ # Landing, privacy, terms, support
│ │ ├── (auth)/ # Sign in, sign up (route group, no URL segment)
│ │ ├── app/ # Authenticated app: dashboard, reviews, templates, settings
│ │ ├── admin/ # Admin-gated: overview, users, settings, AI config
│ │ ├── analyze/ # Public scope analyzer (guest-capable)
│ │ ├── result/[slug]/ # Shared report viewer (noindex)
│ │ ├── api/ # Route handlers (Zod-validated)
│ │ ├── globals.css # Tailwind v4 + brand theme tokens
│ │ ├── layout.tsx # Root layout (fonts, providers, metadata)
│ │ ├── error.tsx # Error boundary
│ │ ├── loading.tsx # Seal loader
│ │ ├── not-found.tsx # Branded 404
│ │ ├── manifest.ts # PWA manifest
│ │ ├── robots.ts # robots.txt route
│ │ └── sitemap.ts # sitemap.xml route
│ ├── components/
│ │ ├── ui/ # 21 shadcn/ui base components (brand-customized)
│ │ ├── brand/ # SealLogo, SealScoreRing, SealLoader, ThemeToggle
│ │ ├── animations/ # Reveal, MagneticButton, CountUp, variants
│ │ ├── site/ # Header, Footer, Hero, Features, HowItWorks, etc.
│ │ ├── auth/ # SignOutButton
│ │ └── providers/ # ThemeProvider (next-themes)
│ ├── lib/
│ │ ├── engine/ # Deterministic analysis engine (pure TS)
│ │ ├── auth.ts # Auth.js v5 config (Credentials, JWT, roles)
│ │ ├── db.ts # Prisma client singleton (PrismaPg adapter)
│ │ ├── crypto.ts # AES-256-GCM encrypt/decrypt for provider keys
│ │ ├── admin-guard.ts # Admin role authorization guard
│ │ ├── ai-client.ts # OpenAI-compatible AI enhancement client
│ │ ├── rate-limit.ts # Per-IP sliding-window rate limiter
│ │ ├── guest-quota.ts # Cookie-based guest report quota
│ │ ├── export.ts # Report → Markdown export
│ │ └── utils.ts # cn() className merge utility
│ ├── generated/prisma/ # Prisma client output (gitignored)
│ └── types/next-auth.d.ts # Session/JWT type augmentation (id, role)
├── prisma/
│ ├── schema.prisma # 7 models, 2 enums
│ └── seed.ts # Admin user, settings, default templates
├── prisma.config.ts # Prisma 7 config (datasource URL, schema path)
├── docs/
│ ├── ARCHITECTURE.md # Architecture, data model, API contract, security
│ └── UI_UX_GUIDELINES.md # Brand, palette, typography, animation, accessibility
├── e2e/
│ └── scope-seal.spec.ts # Playwright E2E suite (23 tests)
├── AGENTS.md # Coding agent instructions (stack, conventions, pitfalls)
├── .env.example # Environment variable documentation
├── vercel.json # Security headers
├── playwright.config.ts
├── vitest.config.ts
├── components.json # shadcn/ui config
└── package.json
┌──────────────────────────────────────────────────────────────────┐
│ Chrome Extension (WXT) │
│ codezelat/scopeseal-chrome-ext │
│ Quick capture · explicit action only · no auto-upload │
└──────────────────────────┬───────────────────────────────────────┘
│ selected text + project type
▼
┌──────────────────────────────────────────────────────────────────┐
│ Next.js 16 App (this repo) │
│ scopeseal.codezela.com │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────────────┐ │
│ │ Public │ │ Authenticated│ │ Admin │ │
│ │ Pages │ │ App (/app) │ │ (/admin) │ │
│ │ + /analyze │ │ Dashboard │ │ Analytics │ │
│ │ + /result │ │ Reviews │ │ User management │ │
│ │ │ │ Templates │ │ Settings │ │
│ │ │ │ Settings │ │ AI config │ │
│ └──────┬──────┘ └──────┬───────┘ └──────────┬────────────┘ │
│ │ │ │ │
│ └────────────────┼──────────────────────┘ │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ API Routes │ Zod-validated input │
│ │ (/api/*) │ Structured error JSON │
│ └───────┬───────────────┘ │
│ │ │
│ ┌────────────┼─────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌──────────┐ ┌────────────────────┐ │
│ │ Engine │ │ Prisma 7 │ │ AI Client │ │
│ │ (pure TS) │ │ → NeonDB │ │ (OpenAI-compat, │ │
│ │ │ │ Postgres │ │ admin-gated, │ │
│ │ Determinism│ │ │ │ AES-encrypted key)│ │
│ └────────────┘ └──────────┘ └────────────────────┘ │
│ │
│ Auth.js v5: Credentials provider, JWT sessions, USER/ADMIN roles │
└──────────────────────────────────────────────────────────────────┘
- Server-first. React Server Components by default;
"use client"only for interactivity, hooks, and Motion animations. - Validation at the edge. Every API route validates input with a Zod schema. No client data is ever trusted.
- Typed API contract. Frozen types in
src/lib/engine/types.tsare shared across server, client, and the Chrome extension. - Structured errors. All API errors return
{ error: string, code: string }with correct HTTP status. Stack traces are never leaked to clients. - Deterministic core. The analysis engine is pure TypeScript with zero server dependencies. The optional AI layer enhances — never replaces — it.
- Security headers on Vercel. Set via
vercel.json(not middleware), to avoid breaking auth redirects.
Database: PostgreSQL on NeonDB, accessed via Prisma 7 with the @prisma/adapter-pg
driver adapter.
| Model | Purpose | Key Fields |
|---|---|---|
| User | Authentication | email (unique), passwordHash (bcrypt), name, role (USER/ADMIN), guestReportsUsed |
| Review | Stored analysis | userId (nullable for guests), projectType, inputText (truncated 5k), score, band, categories/missing/risks/suggestions/outputs (JSON), shareSlug (unique, 12-char), isShared |
| Template | Reusable scope templates | projectType, title, body, sortOrder |
| Setting | Key-value platform config | key (unique), value (JSON) — stores aiModeEnabled, guestQuota, rateLimit, branding |
| AiConfig | Singleton AI provider config | provider, baseUrl, apiKeyEncrypted (AES-256-GCM), model, enabled |
| Account | Auth.js adapter (future OAuth) | Standard NextAuth fields |
| Session | Auth.js adapter (unused — JWT) | Standard NextAuth fields |
| VerificationToken | Auth.js adapter | Standard NextAuth fields |
UserRole:USER,ADMINReviewBand:clear,review,risky
The seed script (prisma/seed.ts) creates:
- An admin user (email from
ADMIN_EMAILenv, bcrypt-hashed password) - 4 platform settings (
aiModeEnabled: false,guestQuota: 3,rateLimit,branding) - 4 default templates (Website, SEO, Maintenance, General) with bracketed placeholders for common scope sections
- Auth.js v5 with the Credentials provider (email + password).
- JWT session strategy (required for Credentials — no database sessions).
- Passwords hashed with bcrypt at cost factor 12.
- Timing-attack mitigation: a 300ms delay is applied when a user lookup fails, to prevent email enumeration.
- Custom
role(USER/ADMIN) propagated through the JWT token → session. getCurrentUser()server-side helper for server components and route handlers.
| Area | Protection |
|---|---|
Public pages (/, /analyze, /result/*, legal pages) |
None |
Authenticated app (/app/*) |
Layout-level auth() check → redirect to /signin |
Admin (/admin/*) |
requireAdmin() → redirect non-admins to /app |
API /api/analyze |
Rate-limited; guests quota-checked; users unlimited |
API /api/analyze/enhance, /api/reviews/*, /api/user/* |
Session required |
API /api/admin/* |
role === "ADMIN" required (returns 403) |
| Measure | Implementation |
|---|---|
| Password hashing | bcrypt, cost factor 12 |
| Provider key encryption | AES-256-GCM (12-byte IV, 16-byte auth tag), key from AI_ENCRYPTION_KEY env. Format: iv:authTag:ciphertext (hex). Keys are never returned to the client in plaintext — only a masked hint (••••last4). |
| Rate limiting | Per-IP sliding-window (10 requests / 60 seconds), runs before everything else. Returns 429 with Retry-After. |
| Guest quota | Cookie-based (ss_guest_count, httpOnly, 30-day), default 3 reports. Bypassed by signing in. |
| Shared reports | Unguessable 12-char slugs + noindex (X-Robots-Tag + meta robots) so they never appear in search. |
| Security headers | X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy, HSTS, Permissions-Policy — via vercel.json. |
| Input validation | Every API route validates with Zod. Analyze endpoint enforces 50–50,000 char range. |
| No stack traces | Errors return structured { error, code } JSON. |
| Extension privacy | The Chrome extension captures text only on explicit user action — no auto-upload, no background scanning, no <all_urls>. |
| Route | Type | Description |
|---|---|---|
/ |
Server | Landing page — hero, how-it-works, project types, features, live score demo, extension CTA |
/analyze |
Server + Client | Scope analyzer — project-type selector, textarea, live word count, "paste example" per type, analysis trigger |
/result/[slug] |
Server + Client | Shared report viewer — full results dashboard (score ring, categories, missing, risks, outputs, export, AI enhance). noindex. |
/privacy |
Server | Privacy policy — data processed, extension permissions, shared reports, AI enhancement, retention |
/terms |
Server | Terms of service — including explicit "no legal advice" clause |
/support |
Server | Support page — email contact, FAQ, extension info |
| Route | Description |
|---|---|
/signin |
Sign-in form (email + password) with server action |
/signup |
Sign-up form (name + email + password, min 8 chars) with server action; auto-login on success |
| Route | Description |
|---|---|
/app |
Dashboard — welcome, review count, quick-link cards, 3 most recent reviews |
/app/reviews |
My reviews — paginated list (20/page) with score chips, badges, text preview |
/app/templates |
Template library — grouped by project type, "Use template" → pre-fills /analyze |
/app/settings |
Account settings — profile name, appearance/theme, change password, delete account |
| Route | Description |
|---|---|
/admin |
Overview — total users, total reviews, average score, reviews this week, band distribution chart, recent activity table |
/admin/users |
User management — searchable, paginated, promote/demote roles (self-demotion guarded) |
/admin/settings |
Global settings — guest quota, site name, maintenance mode |
/admin/ai-config |
AI provider config — enable/disable, provider select, base URL, API key (masked), model, test connection |
| Route | Description |
|---|---|
/robots.txt |
Allows /, disallows /app, /admin, /api, /result/ |
/sitemap.xml |
Lists /, /analyze, /signin, /signup |
/manifest.webmanifest |
PWA manifest (ScopeSeal, standalone, dark theme) |
All API routes validate input with Zod and return structured errors.
Successful responses return JSON; errors return { error: string, code: string }
with the appropriate HTTP status.
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/api/analyze |
Guest (quota) or User | Run analysis. Input: { text (50–50k), projectType }. Returns { result, reviewId, shareSlug, guestQuota }. Codes: RATE_LIMITED (429), QUOTA_EXCEEDED (403), VALIDATION_ERROR (400). |
POST |
/api/analyze/enhance |
User | AI enhancement. Input: { scopeText, projectType }. Returns { rewrittenScope, improvements }. Codes: UNAUTHORIZED (401), AI_UNAVAILABLE (503). |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/api/templates |
Public | List all templates ordered by sortOrder. Cached (s-maxage=300, SWR=600). |
| Method | Route | Auth | Description |
|---|---|---|---|
DELETE |
/api/reviews/[id] |
Owner | Delete a review. 403 if not owner, 404 if not found. |
PATCH |
/api/reviews/[id]/share |
Owner | Toggle isShared. Returns { isShared, shareUrl }. |
| Method | Route | Auth | Description |
|---|---|---|---|
PATCH |
/api/user/update-name |
User | Update display name (1–100 chars). |
PATCH |
/api/user/change-password |
User | Change password (verify current, min 8 new). Code: INVALID_PASSWORD (400). |
DELETE |
/api/user/delete |
User | Permanently delete account (cascades to reviews). |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/api/admin/ai-config |
Admin | Get AI config (provider, baseUrl, model, enabled, hasKey, masked keyHint). |
PUT |
/api/admin/ai-config |
Admin | Upsert AI config. apiKey is encrypted before storage. Empty key removes it. |
PATCH |
/api/admin/ai-config |
Admin | Toggle enabled only. |
POST |
/api/admin/ai-config/test |
Admin | Test AI connection — decrypts key, calls {baseUrl}/models, verifies model exists. 15s timeout. |
GET |
/api/admin/settings |
Admin | List all settings. |
PATCH |
/api/admin/settings |
Admin | Upsert a single setting by key. |
PATCH |
/api/admin/users/[id]/role |
Admin | Toggle user role. Code: SELF_DEMOTE (400) if admin demotes themselves. |
| Method | Route | Description |
|---|---|---|
GET, POST |
/api/auth/[...nextauth] |
Auth.js catch-all handler. |
The brand metaphor is a precision lens (scope) × a wax seal of approval (seal) — uniting engineering precision with institutional trust. The signature interaction is an animated circular seal-stamp that stamps down to reveal the score, like a notary stamping a document.
Defined as Tailwind v4 @theme tokens in src/app/globals.css:
| Token | Hex | Purpose |
|---|---|---|
ink-950 |
#070B22 |
Deepest dark background |
ink-900 |
#0A0F2C |
Primary dark base |
ink-800 |
#141B3F |
Raised dark surfaces |
ink-700 |
#1F2A55 |
Dark borders |
seal-violet |
#8B5CF6 |
Primary accent / glow start |
seal-indigo |
#6366F1 |
Accent end / interactive |
seal-cyan |
#22D3EE |
Subtle highlight (used sparingly) |
surface |
#F8FAFC |
Light background |
clear |
#10B981 |
Score band "clear" (emerald) |
risk |
#F59E0B |
Score band "review" (amber) |
missing |
#F43F5E |
Score band "risky" (rose) |
Primary gradient: linear-gradient(135deg, #8B5CF6 → #6366F1) — used on CTAs,
the score ring, and brand accents. Dark mode is first-class (premium feel);
light mode is fully supported and accessible.
Loaded via next/font/google with zero layout shift:
| Role | Font | Character |
|---|---|---|
| Display / headings | Sora (variable, 600–800) | Geometric, precise — the "scope" voice |
| Body / UI | Geist (variable, 400–600) | Crisp, modern, highly legible |
| Numerals / code | Geist Mono | Score numerals, word counts, code-like data |
| Component | Description |
|---|---|
SealLogo |
28×28 SVG seal mark (reticle ring + cardinal ticks + center dot) + "ScopeSeal" wordmark with gradient |
SealScoreRing |
The flagship score gauge — 3-stage spring animation (ring sweep → number pop with rotation → band pill), useId-scoped gradient, accessible aria-label |
SealLoader |
Seal-themed spinner — rotating gradient dashes + cardinal ticks, reduced-motion aware |
ThemeToggle |
Light/dark toggle with hydration-safe mount gate |
Confident, practical, agency-aware, non-legal:
| ✅ Good | ❌ Bad |
|---|---|
| "Revision limits are not clearly defined." | "This contract is invalid." |
| "This may create scope creep if the client expects unlimited changes." | "Legal risk detected." |
| "Define a specific limit." | "You will lose money." |
- WCAG AA contrast on all text and controls (both themes)
- Keyboard-first: visible focus rings, logical tab order, escape-to-close, enter-to-submit
- Semantic landmarks (
header,nav,main,footer), aria labels on icon-only controls prefers-reduced-motionrespected by every Motion variant- Score ring has an accessible text label alongside the visual
All animations use Motion (motion/react) and respect
prefers-reduced-motion.
| # | Animation | Where |
|---|---|---|
| 1 | Seal-stamp score reveal — ring draws, rotates, scales-in to reveal the score | SealScoreRing |
| 2 | Staggered fade-up — sections/cards reveal in sequence on scroll | Reveal, landing sections |
| 3 | Spring-in cards — risk/missing cards pop with spring on mount | Result view |
| 4 | Magnetic primary CTA — button nudges toward cursor on hover | MagneticButton |
| 5 | Seal-ring loader — rotating dashed ring during analysis | SealLoader |
| 6 | Count-up — numbers animate from 0 on view | CountUp, score demo |
| 7 | Micro-interactions — tap-scale on buttons, hover-lift on cards, animated underlines | Throughout |
| 8 | Animated progress bars — category bars fill on whileInView |
Score demo, result view |
Located in src/lib/engine/__tests__/engine.test.ts.
Coverage includes:
- Determinism — identical inputs produce deeply-equal outputs
- Empty/whitespace — score 0, band risky, all categories 0
- Vague brief — score < 45, risky/review band, correct missing items and risks
- Detailed scope — score ≥ 70, clear band, few missing items, few risks
- All 8 project types — valid weights, valid results, 9 categories
- Risky-phrase detector — multi-occurrence counts, context snippets, negation awareness ("not unlimited" not flagged)
- Missing items — barebones brief misses payment/timeline/revisions; detailed scope doesn't
- Outputs — all 4 copy-ready outputs non-empty, no legal-advice claims
- Sensitive content — credit cards, SSNs, email+password, NDA detection
- Very long text — ~2000 words processed in < 5s without crashing
- 8 QA scenario inputs — vague brief, detailed scope, SEO, social, software, maintenance, WhatsApp message, professional proposal
- Text utilities — word count, sentence splitting, phrase finding, negation
- Calibration regression — detailed website scores ≥ 70, vague stays < 45, hyphenated/slash terms matched
pnpm testLocated in e2e/scope-seal.spec.ts. Chromium-only,
auto-starts the dev server on port 3001. Coverage includes:
- Landing page — hero H1, project types, footer → legal page navigation
- Analyze flow — paste text → analyze → score visible, no application error
- 9 QA scenarios — vague brief, detailed scope, SEO, social, software, maintenance, WhatsApp message, professional proposal, very long text
- Button disabled state — analyze button disabled when textarea empty
- Auth pages — signin/signup render with correct headings and form fields
- No legal advice — landing page doesn't mention "legal advice", "attorney", or "contract is invalid"
- Public pages — privacy, terms, support, robots.txt, sitemap.xml, manifest.webmanifest, branded 404
pnpm test:e2epnpm type-check && pnpm lint && pnpm build- Node.js managed via nvm
- pnpm package manager
- A NeonDB Postgres database (or any Postgres instance)
# 1. Install dependencies
pnpm install
# 2. Copy environment variables and fill them in
cp .env.example .env
# Edit .env: set DATABASE_URL, AUTH_SECRET, AI_ENCRYPTION_KEY, ADMIN_EMAIL, ADMIN_PASSWORD
# 3. Generate the Prisma client
pnpm dlx prisma generate
# 4. Push the schema to your database
pnpm dlx prisma db push
# 5. Seed the database (admin user, settings, templates)
pnpm db:seed
# 6. Start the dev server
pnpm devOpen http://localhost:3000.
If you use nvm, prefix all npm/node/pnpm commands:
source ~/.nvm/nvm.sh && pnpm devpnpm prod:setup # prisma generate && prisma db push && seed
pnpm build
pnpm startAll variables are documented in .env.example. Copy it to
.env and fill in the values.
| Variable | Description |
|---|---|
DATABASE_URL |
NeonDB Postgres pooler URL. Use -pooler endpoint, keep ?sslmode=require, remove channel_binding=require (Prisma's pg adapter doesn't support it). |
AUTH_SECRET |
JWT signing secret. Generate with openssl rand -base64 32. |
AUTH_TRUST_HOST |
Trusted production host (e.g. https://scopeseal.codezela.com). |
AI_ENCRYPTION_KEY |
32-byte hex key (64 chars) for AES-256-GCM encryption of provider API keys. Generate with openssl rand -hex 32. |
| Variable | Default | Description |
|---|---|---|
NEXT_PUBLIC_APP_NAME |
ScopeSeal |
Public app name |
NEXT_PUBLIC_APP_URL |
https://scopeseal.codezela.com |
Public app URL |
GUEST_REPORT_QUOTA |
3 |
Max guest analyses before sign-in required |
RATE_LIMIT_WINDOW_SECONDS |
60 |
Rate limit window (documented; limiter currently uses hardcoded 60s) |
RATE_LIMIT_MAX_REQUESTS |
10 |
Max requests per window (documented; limiter currently uses hardcoded 10) |
ADMIN_EMAIL |
admin@codezela.com |
Seed admin email |
ADMIN_PASSWORD |
— | Seed admin password (skipped if unset) |
UPSTASH_REDIS_REST_URL |
— | Optional distributed rate limiting (in-memory used if unset) |
UPSTASH_REDIS_REST_TOKEN |
— | Optional Upstash Redis token |
OPENAI_API_KEY |
— | Optional server-side AI fallback (admin config preferred) |
OPENAI_BASE_URL |
https://api.openai.com/v1 |
Optional AI fallback base URL |
OPENAI_MODEL |
gpt-4o-mini |
Optional AI fallback model |
| Script | Command | Purpose |
|---|---|---|
dev |
next dev |
Start dev server (Turbopack) |
build |
next build |
Production build |
start |
next start |
Start production server |
lint |
eslint |
Lint the codebase |
type-check |
tsc --noEmit |
TypeScript type check (strict) |
test |
vitest run |
Run unit tests |
test:e2e |
playwright test |
Run E2E tests (auto-starts dev server) |
postinstall |
prisma generate |
Auto-generate Prisma client (for Vercel npm ci) |
db:push |
prisma db push |
Push schema to database |
db:seed |
node --env-file-if-exists=.env --import tsx prisma/seed.ts |
Seed database (tsx doesn't load .env automatically) |
prod:setup |
prisma generate && prisma db push && db:seed |
Full production setup |
| Document | Description |
|---|---|
docs/ARCHITECTURE.md |
Architecture diagram, data model, engine internals, API contract, security model |
docs/UI_UX_GUIDELINES.md |
Brand metaphor, color palette, typography, component patterns, animation inventory, tone of voice, accessibility |
AGENTS.md |
Instructions for coding agents — stack, shell commands, brand/design system, conventions, known pitfalls |
The ScopeSeal Chrome extension is a separate repository that provides quick capture and lightweight review directly from the browser:
- Repo:
codezelat/scopeseal-chrome-ext - Chrome Web Store: Install ScopeSeal
The extension uses explicit action only — it captures selected text only
after you trigger it. There is no auto-upload, no background scanning, and no
<all_urls> permission. It shares the same deterministic analysis engine and
typed API contract as the web app.
| Repository | Description |
|---|---|
codezelat/scopeseal |
This repo — the full web app |
codezelat/scopeseal-chrome-ext |
Chrome extension — quick capture + lightweight review |
This is a proprietary product. Copyright © 2026 Codezela Technologies. All
rights reserved. No license is granted to use, copy, modify, or distribute this
software without prior written permission. See LICENSE for full
terms.
For licensing inquiries, contact: info@codezela.com
ScopeSeal · Built by Codezela Technologies
Made for agencies, freelancers & delivery teams.