╔══════════════════════════════════════════════════════════════════════╗ ║ AUDITMY.SITE — LARAVEL APPLICATION BUILD INSTRUCTIONS ║ ║ For use with Claude Code on cPanel terminal ║ ╚══════════════════════════════════════════════════════════════════════╝ ======================================================================= HOW TO USE THIS FILE ======================================================================= 1. Upload this file (instructions.txt) AND the JSX preview file (audit-preview.jsx) to your server — e.g. /home/yourusername/ 2. Open your cPanel terminal 3. Run Claude Code with this prompt: ─────────────────────────────────────────────────────────────────────── PROMPT TO PASTE INTO CLAUDE CODE ─────────────────────────────────────────────────────────────────────── Read the instructions file at /home/auditmysitedevel/instructions.txt and the JSX preview at /home/auditmysitedevel/audit-preview.jsx carefully. Build the complete Laravel 10 application for AuditMy.Site following every specification in the instructions. The JSX file shows the exact UI and data format the frontend expects — the backend MUST produce JSON matching that structure. The application should be created at /home/auditmysitedevel/auditmy-site/ Start by reading both files in full, then work through the instructions section by section. Create every file listed. Test each PHP file for syntax errors after creating it. ─────────────────────────────────────────────────────────────────────── END OF PROMPT ─────────────────────────────────────────────────────────────────────── ======================================================================= TABLE OF CONTENTS ======================================================================= 1. PROJECT OVERVIEW 2. TECH STACK & DEPENDENCIES 3. ENVIRONMENT VARIABLES 4. DATABASE SCHEMA (MIGRATION) 5. FILE STRUCTURE 6. DATA FORMAT SPECIFICATION (CRITICAL) 7. BACKEND: PageFetcher 8. BACKEND: OnPageAnalyser (SEO — with evidence) 9. BACKEND: TechnicalAnalyser (SEO — with evidence) 10. BACKEND: ContentAnalyser (SEO — with evidence) 11. BACKEND: CroAiAnalyser (AI — desktop + mobile split) 12. BACKEND: SeoAuditor (orchestrator) 13. BACKEND: AuditController (API + Stripe + webhooks) 14. BACKEND: AdminController (dashboard) 15. FRONTEND: audit.blade.php (main tool) 16. FRONTEND: Admin views 17. ROUTES 18. CONFIG FILES 19. DEPLOYMENT NOTES ======================================================================= 1. PROJECT OVERVIEW ======================================================================= AuditMy.Site is a paid SEO + CRO audit tool. Flow: 1. User enters a URL 2. BOTH analyses run immediately: a. SEO audit runs instantly (code-based, ~2 seconds) b. CRO audit triggers in parallel (Claude API, ~10-20 seconds) 3. User sees FREE PARTIAL REPORT: - SEO: All 3 category scores + first 3 findings per category (title + badge only, no description/evidence/recommendation). Remaining findings shown blurred behind paywall. - CRO: Desktop + Mobile scores + first 1 finding per category (title + badge only). Remaining findings blurred. - Overall scores visible (SEO, CRO Desktop, CRO Mobile) 4. PAYWALL: "Unlock Full Report — £14.99" - User enters name + email → lead saved - User pays via Stripe Checkout 5. After payment → FULL REPORT unlocks: - ALL SEO findings with full descriptions, evidence, and recommendations (expandable/collapsible cards) - ALL CRO findings (desktop + mobile) with full descriptions, evidence, and recommendations - Priority Actions tab (top 10 fixes ranked by severity) Two types of analysis: - SEO (code-based): Instant, deterministic. Runs PHP code against the HTML DOM. 22+ checks across 3 categories. Partially shown for free, full details behind paywall. - CRO (AI-based): Uses Claude Haiku 4.5 API. Two separate API calls — one for desktop CRO, one for mobile CRO. Each returns 4-5 categories with 3-5 findings per category. Partially shown for free, full details behind paywall. IMPORTANT — The CRO analysis runs for FREE on every audit. This is a deliberate choice: showing the user their CRO scores and a taste of the AI findings makes the paywall far more compelling than only showing SEO data. The API cost per audit is ~£0.03 (Haiku 4.5), so even if only 5% of users pay, each paying user covers the cost of 250 free audits. Branding: Powered by Visionsharp (configurable in .env) ======================================================================= 2. TECH STACK & DEPENDENCIES ======================================================================= composer.json: { "name": "visionsharp/auditmy-site", "type": "project", "description": "SEO + AI-powered CRO audit tool", "require": { "php": "^8.1", "laravel/framework": "^10.0", "guzzlehttp/guzzle": "^7.8", "stripe/stripe-php": "^13.0", "ext-dom": "*", "ext-mbstring": "*", "ext-curl": "*" }, "require-dev": { "fakerphp/faker": "^1.23", "phpunit/phpunit": "^10.1" }, "autoload": { "psr-4": { "App\\": "app/" } }, "minimum-stability": "stable", "prefer-stable": true } ======================================================================= 3. ENVIRONMENT VARIABLES ======================================================================= Create .env.example with these variables: APP_NAME=AuditMySite APP_ENV=production APP_KEY= APP_DEBUG=false APP_URL=https://auditmy.site DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=auditmy_site DB_USERNAME=root DB_PASSWORD= # Stripe STRIPE_KEY=pk_live_xxx STRIPE_SECRET=sk_live_xxx STRIPE_WEBHOOK_SECRET=whsec_xxx # Anthropic (Claude API for CRO) ANTHROPIC_API_KEY=sk-ant-xxx ANTHROPIC_MODEL=claude-haiku-4-5-20251001 # Audit config AUDIT_PRICE=14.99 AUDIT_CURRENCY=gbp AUDIT_TIMEOUT=15 AUDIT_USER_AGENT=AuditMySite/1.0 # Branding BRAND_NAME=Visionsharp BRAND_URL=https://www.visionsharp.co.uk CONTACT_URL=https://www.visionsharp.co.uk/about-us/contact-us/ # Queue (for async CRO) QUEUE_CONNECTION=database ======================================================================= 4. DATABASE SCHEMA ======================================================================= Create migration: database/migrations/2026_02_25_000001_create_tables.php Table: audits - id: uuid, primary key - url: string(500) - host: string(255), indexed - seo_score: integer, default 0 - cro_score: integer, default 0 - cro_desktop_score: integer, default 0 ← NEW - cro_mobile_score: integer, default 0 ← NEW - overall_score: integer, default 0 - seo_result_json: longText, nullable - cro_result_json: longText, nullable (stores BOTH desktop+mobile) - full_result_json: longText, nullable - cro_status: enum(pending,processing,complete,failed), default pending - ip_address: string(45), nullable - user_agent: string(500), nullable - referrer: string(500), nullable - timestamps - index on created_at Table: leads - id: uuid, primary key - name: string - email: string, indexed - company: string, nullable - phone: string(50), nullable - audit_id: uuid, nullable, indexed - url: string(500), nullable - score: integer, nullable - source: string, default 'audit' - timestamps Table: payments - id: uuid, primary key - audit_id: uuid, indexed - lead_id: uuid, nullable, indexed - stripe_session_id: string, nullable, unique - stripe_payment_intent: string, nullable - stripe_customer_id: string, nullable - amount: integer (in pence) - currency: string(3), default 'gbp' - status: enum(pending,processing,paid,failed,refunded), default pending - email: string, nullable - stripe_metadata: json, nullable - timestamps - index on status, created_at Table: admin_users - id: auto-increment - name: string - email: string, unique - password: string - remember_token - timestamps Table: webhook_logs - id: auto-increment - event_type: string - stripe_event_id: string, nullable - payload: json, nullable - status: enum(processed,failed,ignored), default processed - error: text, nullable - timestamps ======================================================================= 5. FILE STRUCTURE ======================================================================= auditmy-site/ ├── app/ │ ├── Http/ │ │ └── Controllers/ │ │ ├── AuditController.php │ │ └── AdminController.php │ └── Services/ │ ├── PageFetcher.php │ ├── SeoAuditor.php │ └── Analysers/ │ ├── OnPageAnalyser.php │ ├── TechnicalAnalyser.php │ ├── ContentAnalyser.php │ └── CroAiAnalyser.php ├── config/ │ ├── audit.php │ └── services.php ├── database/ │ └── migrations/ │ └── 2026_02_25_000001_create_tables.php ├── resources/ │ └── views/ │ ├── audit.blade.php │ └── admin/ │ ├── layout.blade.php │ ├── dashboard.blade.php │ ├── audits.blade.php │ ├── audit-detail.blade.php │ ├── leads.blade.php │ ├── payments.blade.php │ ├── webhooks.blade.php │ └── pagination.blade.php ├── routes/ │ └── web.php ├── composer.json ├── .env.example └── README.md ======================================================================= 6. DATA FORMAT SPECIFICATION (CRITICAL — READ CAREFULLY) ======================================================================= This is the EXACT JSON format the frontend expects. The backend MUST produce data matching this structure. Look at the JSX preview file for the full example data. ─── SEO Finding Format ─── Every SEO finding (from OnPageAnalyser, TechnicalAnalyser, ContentAnalyser) MUST use this format: { "s": "pass|warn|fail|info", "t": "Short title with specific data, e.g. 'Title tag: \"My Title\" (42 chars)'", "d": "2-3 sentence description explaining why this matters", "r": "Specific actionable recommendation (null for pass findings)", "evidence": [ {"label": "Current title", "value": "\"My Title Here\"", "color": "#F5C542"}, {"label": "Length", "value": "42 characters", "color": "#22C97A"}, ... ], "evidenceLabel": "Title tag details" } CRITICAL: The "evidence" array is what makes this tool premium. Every finding MUST include an evidence array showing the actual data found on the page. This is what the user is paying for. Examples of evidence by check type: Title tag: - Current title text (exact) - Character count - Optimal range Meta description: - Current description text (exact, or "Not found") - Character count Headings: - Every H1 tag with its full text - Every H2 tag with its full text - Every H3 tag with its full text - Count of each level Images: - Total count - Count with alt / missing alt / empty alt - List of specific image src paths that are missing alt - For images WITH alt, show the alt text too if there are few Links: - Count of internal (unique) vs total - Count of external - List of unique internal link destinations - List of external link destinations Open Graph: - Status of each OG tag (og:title, og:description, og:image, og:url, og:type) — show actual value or "MISSING" Canonical: - The actual canonical URL found SSL: - Protocol (HTTP vs HTTPS) - Certificate status Response time: - Actual TTFB in milliseconds - Page size in bytes/KB - Redirect count Structured data: - JSON-LD found: yes/no (if yes, show the @type values) - Microdata found: yes/no - RDFa found: yes/no Robots.txt: - Status code - Key directives found (Disallow, Sitemap) Sitemap: - Location URL - Number of URLs listed Word count: - Exact word count - Paragraph count And so on for every check... The key principle: if you found specific data on the page, SHOW IT in the evidence array. Never just say "2 H1 tags found" — show the actual text of both H1 tags. ─── Evidence "color" Values ─── Use these hex colours in evidence items to indicate status: Pass/good: "#22C97A" Warning: "#F5C542" Fail/bad: "#EF5350" Neutral/info: "#7B8CA2" (or omit color for default white) ─── SEO Category Format ─── { "id": "onpage", "title": "On-Page SEO", "accent": "#00E09E", "findings": [ ...finding objects... ], "score": 42 } Categories: onpage → accent "#00E09E" → title "On-Page SEO" technical → accent "#6C8EEF" → title "Technical SEO" content → accent "#A78BFA" → title "Content Quality" ─── Full SEO API Response (POST /api/audit) ─── { "success": true, "audit_id": "uuid", "url": "https://example.com", "final_url": "https://www.example.com", "host": "www.example.com", "seo_score": 47, "seo_categories": [ ...category objects... ], "seo_priorities": [ ...priority objects... ], "meta": { "status_code": 200, "response_time": 1847, "page_size": 287400, "is_https": true, "redirect_count": 1, "audited_at": "2026-02-25T14:30:00+00:00" } } ─── CRO Finding Format ─── CRO findings come from the Claude API. The prompt instructs Claude to return findings in this SAME format: { "s": "warn", "t": "Short specific title referencing this site", "d": "2-3 sentence analysis specific to this page, citing research", "r": "Specific actionable recommendation with examples", "evidence": [ {"label": "Hero CTA", "value": "\"Contact Us\"", "color": "#F5C542"}, {"label": "Footer CTA", "value": "\"Send Message\"", "color": "#F5C542"} ], "evidenceLabel": "All CTA buttons on page" } IMPORTANT: The Claude API prompt MUST instruct Claude to include evidence arrays in its findings. This is what makes the CRO analysis premium — showing the actual button copy, form fields, heading text etc. that Claude found on the page. ─── CRO Response Format (stored in cro_result_json) ─── { "cro_score": 38, "cro_desktop_score": 42, "cro_mobile_score": 31, "desktop": { "categories": [ { "id": "cta", "title": "Calls to Action", "accent": "#F59E0B", "score": 35, "findings": [ ...findings with evidence... ] }, ...4 more categories ] }, "mobile": { "categories": [ { "id": "cta_mob", "title": "Mobile CTAs & Touch Targets", "accent": "#F59E0B", "score": 22, "findings": [ ...findings with evidence... ] }, ...3-4 more categories ] } } ─── CRO Status Endpoint (GET /api/audit/{id}/cro-status) ─── Returns scores only (not full findings): { "status": "complete", "cro_score": 38, "cro_desktop_score": 42, "cro_mobile_score": 31 } Or while processing: { "status": "processing" } ─── FREE vs PAID Data (GET /api/audit/{id}) ─── This endpoint checks if a paid payment exists for the audit. When NOT PAID (is_paid: false), findings are stripped down: { "is_paid": false, "seo_score": 47, "cro_score": 38, "cro_desktop_score": 42, "cro_mobile_score": 31, "overall_score": 43, "host": "example.com", "meta": { ...full tech meta... }, "seo_categories": [ { "id": "onpage", "title": "On-Page SEO", "score": 42, "accent": "#00E09E", "total_findings": 9, "findings": [ {"s": "warn", "t": "Title tag: too short (20 chars)"}, {"s": "fail", "t": "Meta description: Missing"}, {"s": "warn", "t": "Headings: 2 H1 tags found"} ] }, ... ], "cro_desktop_categories": [ { "id": "cta", "title": "Calls to Action", "score": 35, "accent": "#F59E0B", "total_findings": 4, "findings": [ {"s": "warn", "t": "Hero CTA says 'Contact Us'"} ] }, ... ], "cro_mobile_categories": [ ...same pattern... ] } Note: each category includes "total_findings" (the real count) so the frontend can display "X more findings hidden". The findings array only contains {s, t} — no d, r, evidence, or evidenceLabel. SEO shows first 3 per category, CRO shows first 1 per category. When PAID (is_paid: true), return EVERYTHING: { "is_paid": true, "seo_score": 47, "cro_score": 38, ...all scores... "seo_categories": [ { ...full categories with ALL findings including s, t, d, r, evidence, evidenceLabel... } ], "cro_desktop_categories": [ ...full... ], "cro_mobile_categories": [ ...full... ], "seo_priorities": [ ...top 10 SEO fixes... ], "cro_priorities": [ ...top 10 CRO fixes... ] } ======================================================================= 7. BACKEND: PageFetcher (app/Services/PageFetcher.php) ======================================================================= Purpose: Fetches a URL and returns HTML + metadata. Uses Guzzle. Key features: - Custom user agent from config('audit.user_agent') - Timeout from config('audit.timeout', 15) - Follow redirects, track redirect count - Record response time (TTFB) - Record final URL after redirects - Record body size in bytes - Record is_https boolean - Record status_code - Separate methods: fetch($url), fetchRobotsTxt($url), fetchSitemap($url) Return format from fetch(): { "success": true, "body": "...", "final_url": "https://www.example.com", "status_code": 200, "response_time": 1847, // milliseconds "body_size": 287400, // bytes "is_https": true, "redirect_count": 1 } ======================================================================= 8. BACKEND: OnPageAnalyser (app/Services/Analysers/OnPageAnalyser.php) ======================================================================= Constructor: receives $html (string), $url (string) Method: analyse() returns array of findings Checks to implement (each returns ONE finding with evidence): 1. TITLE TAG - Extract