Book a call
Qualification
You are one step closer to getting more clients coming through your doors.
Let's find out a bit about you!
Take this quick 5 minute form to help us find out more about your business.
// ============================================================ // Clinic Website Grader — Lead Magnet // Newlead Digital — Framer Code Component // ============================================================ import React, { useState, useEffect } from "react" import { addPropertyControls, ControlType } from "framer" // ============================================================ // STATIC DATA // ============================================================ const BASE_COLORS = { bg: "#08090D", card: "#0F1117", cardBorder: "#1E2030", green: "#00D4AA", red: "#FF6B6B", orange: "#FFB347", text: "#FFFFFF", muted: "#8892A4", subtle: "#2A3040", } const QUESTIONS = [ { text: "Is your website mobile-friendly?", why: "65% of patients browse on their phone", impact: 1200, problem: "Not mobile-friendly — you're losing 65% of potential patients the moment they land from their phone. Most will leave without a second thought.", fix: "Fix: Mobile-optimised redesign that converts visitors into bookings on any device", }, { text: "Can patients book appointments directly through your website?", why: "67% of patients prefer online booking over calling", impact: 2400, problem: "No online booking — you're forcing 67% of patients who prefer booking online to call during business hours. Many simply won't bother.", fix: "Fix: Online booking integration synced directly to your practice management system", }, { text: "Does your website load in under 3 seconds?", why: "Every extra second reduces conversions by 7%", impact: 800, problem: "Slow loading — a 3-second delay costs you 21% of visitors before they see a single word about your clinic.", fix: "Fix: Speed optimisation — image compression, caching, and a proper hosting upgrade", }, { text: "Is there a clear 'Book Now' or 'Call Us' button visible without scrolling?", why: "Patients decide in 3 seconds whether to stay or leave", impact: 600, problem: "No visible CTA — if patients can't see how to book within 3 seconds of landing, they leave. Your competitor is one click away.", fix: "Fix: Sticky header with a prominent, high-contrast Book Now button above the fold", }, { text: "Do you send automated appointment reminders (SMS or email)?", why: "Reduces no-shows by 60–80%", impact: 2400, problem: "No automated reminders — your no-show rate is likely 20–40% higher than it needs to be, costing real chair time every single day.", fix: "Fix: Automated SMS/email reminder sequence at 72h, 24h, and 2h before each appointment", }, { text: "Do you have an automated recall system for periodic checkups?", why: "40–60% of patients never return without follow-up", impact: 3600, problem: "No recall system — up to 60% of your patients won't return without a nudge. They're not disloyal, they're just busy.", fix: "Fix: Automated recall campaign triggered by appointment type and treatment interval", }, { text: "Can patients reach you or get answers outside business hours?", why: "78% of patients book with the first clinic that responds", impact: 1800, problem: "No after-hours coverage — 78% of patients book with whoever responds first. When you're closed, that's always your competitor.", fix: "Fix: AI-powered chat or SMS auto-responder that captures and qualifies new leads 24/7", }, { text: "Do you have 30+ Google reviews with a 4.5+ star rating?", why: "3x more patients choose clinics with strong reviews", impact: 1500, problem: "Weak online reputation — patients actively avoid clinics with low review counts or ratings below 4.5 stars. It's the first thing they check.", fix: "Fix: Reputation management system to systematically build and protect your Google presence", }, { text: "Do you systematically ask patients for reviews after visits?", why: "Clinics that ask get 4x more reviews than those that don't", impact: 900, problem: "Not asking for reviews consistently — you're leaving 4x more reviews on the table every month compared to clinics that do this right.", fix: "Fix: Automated post-appointment review request via SMS and email with smart send timing", }, { text: "Do you follow up with patients who received a quote but haven't booked treatment?", why: "10–20% of lost treatment revenue is recoverable", impact: 2100, problem: "No treatment follow-up — 10–20% of patients who got a quote are still deciding. A single, well-timed follow-up often converts them.", fix: "Fix: Automated treatment follow-up sequence with personalised messaging and a clear next step", }, ] const CLINIC_TYPES = [ { icon: "🦷", label: "Dental" }, { icon: "🏥", label: "GP & Medical" }, { icon: "🤸", label: "Physio & Allied Health" }, { icon: "👨⚕️", label: "Specialist" }, { icon: "🏪", label: "Other" }, ] const CATEGORIES = [ { label: "Website Performance", indices: [0, 1, 2, 3] }, { label: "Patient Communication", indices: [4, 5, 6] }, { label: "Reputation & Reviews", indices: [7, 8] }, { label: "Revenue Recovery", indices: [5, 9] }, ] // ============================================================ // HELPERS // ============================================================ function calcScore(answers, indices) { const earned = indices.reduce((sum, i) => { if (answers[i] === "YES") return sum + 10 if (answers[i] === "NOT SURE") return sum + 3 return sum }, 0) return Math.round((earned / (indices.length * 10)) * 100) } function getScoreColor(score, colors) { if (score >= 80) return colors.green if (score >= 60) return colors.orange if (score >= 40) return "#FF8C42" return colors.red } function getScoreLabel(score) { if (score >= 80) return "Excellent" if (score >= 60) return "Good — Room to Grow" if (score >= 40) return "Needs Improvement" return "Critical — Losing Patients Daily" } function fmtMoney(n) { return n.toLocaleString("en-AU") } // ============================================================ // REUSABLE SUB-COMPONENTS (defined outside main component) // ============================================================ function ScoreRing({ score, displayed, size, colors }) { const r = size / 2 - 14 const circ = 2 * Math.PI * r const color = getScoreColor(score, colors) const offset = circ - (displayed / 100) * circ return ( <div style={{ position: "relative", width: size, height: size, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, }} > <svg width={size} height={size} style={{ position: "absolute", top: 0, left: 0, transform: "rotate(-90deg)", }} > <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke="#1E2030" strokeWidth="10" /> <circle cx={size / 2} cy={size / 2} r={r} fill="none" stroke={color} strokeWidth="10" strokeDasharray={circ} strokeDashoffset={offset} strokeLinecap="round" style={{ transition: "stroke-dashoffset 0.04s linear" }} /> </svg> <div style={{ textAlign: "center", position: "relative", zIndex: 1 }} > <div style={{ fontSize: size * 0.24, fontWeight: "800", color, lineHeight: 1, fontFamily: "DM Sans, sans-serif", }} > {displayed} </div> <div style={{ fontSize: size * 0.1, color: colors.muted, marginTop: 2, fontFamily: "DM Sans, sans-serif", }} > / 100 </div> </div> </div> ) } function AnimatedBar({ score, animate, colors, delay }) { const [width, setWidth] = useState(0) const color = getScoreColor(score, colors) useEffect(() => { if (!animate) { setWidth(0) return } const t = setTimeout(() => setWidth(score), delay || 0) return () => clearTimeout(t) }, [animate, score, delay]) return ( <div style={{ height: 8, background: "#1E2030", borderRadius: 4, overflow: "hidden", marginTop: 8, }} > <div style={{ height: "100%", width: width + "%", background: color, borderRadius: 4, transition: "width 1s cubic-bezier(0.16, 1, 0.3, 1)", }} /> </div> ) } function ScoreBadge({ score, colors }) { const color = getScoreColor(score, colors) return ( <span style={{ background: color + "20", border: "1px solid " + color + "44", color, fontSize: 12, fontWeight: 600, padding: "3px 10px", borderRadius: 20, fontFamily: "DM Sans, sans-serif", }} > {score}% </span> ) } // ============================================================ // MAIN COMPONENT // ============================================================ export default function ClinicWebsiteGrader({ bookingUrl, accentColor, }) { const resolvedBookingUrl = bookingUrl || "https://calendly.com/newlead-digital" const resolvedAccent = accentColor || "#4A9EFF" const colors = { ...BASE_COLORS, accent: resolvedAccent } // ── State ──────────────────────────────────────────────── const [step, setStep] = useState(0) const [clinicType, setClinicType] = useState(null) const [answers, setAnswers] = useState(Array(10).fill(null)) const [formData, setFormData] = useState({ name: "", clinicName: "", email: "", phone: "", }) const [formErrors, setFormErrors] = useState({}) const [visible, setVisible] = useState(true) const [displayed, setDisplayed] = useState(0) const [barsGo, setBarsGo] = useState(false) const [hoveredClinic, setHoveredClinic] = useState(null) const [hoveredAnswer, setHoveredAnswer] = useState(null) const [hoveredBtn, setHoveredBtn] = useState(false) // ── Derived ────────────────────────────────────────────── const totalScore = answers.reduce((sum, a) => { if (a === "YES") return sum + 10 if (a === "NOT SURE") return sum + 3 return sum }, 0) const catScores = CATEGORIES.map(({ indices }) => calcScore(answers, indices) ) const revenueLost = answers.reduce((sum, a, i) => { if (a === "NO") return sum + QUESTIONS[i].impact return sum }, 0) // ── Progress ───────────────────────────────────────────── const progressPct = step === 0 ? 0 : step >= 12 ? 100 : ((step - 1) / 11) * 100 const showProgress = step >= 1 && step <= 12 // ── Font injection ─────────────────────────────────────── useEffect(() => { const id = "dm-sans-font" if (document.getElementById(id)) return const link = document.createElement("link") link.id = id link.rel = "stylesheet" link.href = "https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;0,9..40,800;1,9..40,400&display=swap" document.head.appendChild(link) }, []) // ── Navigation ─────────────────────────────────────────── function go(next) { setVisible(false) setTimeout(() => { setStep(next) setVisible(true) try { window.scrollTo({ top: 0, behavior: "smooth" }) } catch (_) {} }, 280) } // ── Score animation ────────────────────────────────────── useEffect(() => { if (step !== 12 && step !== 13) return setDisplayed(0) setBarsGo(false) let frame = 0 const totalFrames = 50 const target = totalScore const id = setInterval(() => { frame++ const eased = 1 - Math.pow(1 - frame / totalFrames, 3) const val = Math.round(eased * target) setDisplayed(val) if (frame >= totalFrames) { setDisplayed(target) clearInterval(id) setTimeout(() => setBarsGo(true), 150) } }, 30) return () => clearInterval(id) }, [step]) // ── Handlers ───────────────────────────────────────────── function pickAnswer(qIdx, val) { const next = [...answers] next[qIdx] = val setAnswers(next) setTimeout(() => go(step + 1), 220) } function submitForm(e) { e.preventDefault() const errs = {} if (!formData.name.trim()) errs.name = "Required" if (!formData.clinicName.trim()) errs.clinicName = "Required" if (!formData.email.trim() || !/\S+@\S+\.\S+/.test(formData.email)) errs.email = "Valid email required" if (!formData.phone.trim()) errs.phone = "Required" if (Object.keys(errs).length) { setFormErrors(errs) return } go(13) } // ── Style helpers ──────────────────────────────────────── const FF = "DM Sans, sans-serif" function cardStyle(extra) { return { background: colors.card, border: "1px solid " + colors.cardBorder, borderRadius: 16, padding: 24, marginBottom: 14, ...extra, } } function primaryBtn(extra) { return { background: "linear-gradient(135deg, " + colors.accent + " 0%, " + colors.accent + "CC 100%)", color: "#fff", border: "none", borderRadius: 12, padding: "17px 28px", fontSize: 16, fontWeight: 700, cursor: "pointer", width: "100%", fontFamily: FF, letterSpacing: 0.3, display: "block", textAlign: "center", textDecoration: "none", boxSizing: "border-box", ...extra, } } // ── SCREEN: Welcome ────────────────────────────────────── function renderWelcome() { return ( <div> <div style={{ textAlign: "center", paddingTop: 12, marginBottom: 32, }} > <div style={{ display: "inline-flex", alignItems: "center", gap: 8, background: colors.accent + "15", border: "1px solid " + colors.accent + "33", borderRadius: 20, padding: "5px 14px", }} > <div style={{ width: 7, height: 7, borderRadius: "50%", background: colors.accent, }} /> <span style={{ color: colors.accent, fontSize: 12, fontWeight: 700, letterSpacing: "1.4px", textTransform: "uppercase", fontFamily: FF, }} > Newlead Digital </span> </div> </div> <div style={cardStyle({ background: "linear-gradient(155deg, #111520 0%, #0D1018 100%)", border: "1px solid " + colors.accent + "22", padding: "44px 32px", textAlign: "center", })} > <div style={{ fontSize: 52, marginBottom: 20, lineHeight: 1, }} > 🏥 </div> <h1 style={{ fontSize: "clamp(26px, 6vw, 38px)", fontWeight: 800, margin: "0 0 16px", lineHeight: 1.2, letterSpacing: "-0.5px", fontFamily: FF, color: colors.text, }} > How Much Revenue Is Your Clinic Leaving on the Table? </h1> <p style={{ color: colors.muted, fontSize: 16, lineHeight: 1.65, margin: "0 0 36px", fontFamily: FF, }} > Take this free 60-second diagnostic. Find out exactly where your website and systems are costing you patients. </p> <button style={primaryBtn({ fontSize: 17, padding: "18px 28px", opacity: hoveredBtn ? 0.88 : 1, })} onClick={() => go(1)} onMouseEnter={() => setHoveredBtn(true)} onMouseLeave={() => setHoveredBtn(false)} > Start My Free Diagnosis → </button> <p style={{ color: colors.muted, fontSize: 13, margin: "14px 0 0", fontFamily: FF, }} > No signup required to start. Results are instant. </p> </div> <div style={{ display: "flex", justifyContent: "center", gap: 20, flexWrap: "wrap", padding: "8px 0 0", }} > {["⚡ 60 seconds", "🎯 Instant results", "🔒 100% free"].map( (t) => ( <span key={t} style={{ color: colors.muted, fontSize: 13, fontFamily: FF, }} > {t} </span> ) )} </div> </div> ) } // ── SCREEN: Clinic type ────────────────────────────────── function renderClinicType() { return ( <div> <div style={{ textAlign: "center", marginBottom: 28 }}> <div style={{ fontSize: 13, fontWeight: 600, letterSpacing: "1.2px", textTransform: "uppercase", color: colors.accent, fontFamily: FF, }} > Step 1 of 11 </div> <h2 style={{ fontSize: "clamp(20px, 5vw, 28px)", fontWeight: 800, margin: "8px 0 4px", fontFamily: FF, color: colors.text, }} > What type of clinic do you run? </h2> <p style={{ color: colors.muted, fontSize: 14, fontFamily: FF, }} > Select your type to get tailored insights </p> </div> <div style={{ display: "flex", flexWrap: "wrap", gap: 12 }}> {CLINIC_TYPES.map(({ icon, label }, i) => { const isLast = i === CLINIC_TYPES.length - 1 const isOdd = CLINIC_TYPES.length % 2 !== 0 const sel = clinicType === label const hov = hoveredClinic === label return ( <button key={label} onClick={() => { setClinicType(label) setTimeout(() => go(2), 180) }} onMouseEnter={() => setHoveredClinic(label)} onMouseLeave={() => setHoveredClinic(null)} style={{ flex: isLast && isOdd ? "1 1 100%" : "1 1 calc(50% - 6px)", background: sel ? colors.accent + "18" : hov ? colors.accent + "0C" : colors.card, border: "2px solid " + (sel ? colors.accent : hov ? colors.accent + "66" : colors.cardBorder), borderRadius: 14, padding: "24px 16px", cursor: "pointer", display: "flex", flexDirection: "column", alignItems: "center", gap: 10, transition: "all 0.18s ease", fontFamily: FF, }} > <div style={{ fontSize: 30 }}>{icon}</div> <div style={{ color: sel ? colors.accent : colors.text, fontSize: 14, fontWeight: 600, fontFamily: FF, }} > {label} </div> </button> ) })} </div> </div> ) } // ── SCREEN: Question ───────────────────────────────────── function renderQuestion(qIdx) { const q = QUESTIONS[qIdx] const opts = [ { val: "YES", icon: "✓", color: colors.green }, { val: "NO", icon: "✗", color: colors.red }, { val: "NOT SURE", icon: "?", color: colors.orange }, ] return ( <div> <div style={{ textAlign: "center", marginBottom: 24 }}> <div style={{ fontSize: 13, fontWeight: 600, letterSpacing: "1.2px", textTransform: "uppercase", color: colors.accent, fontFamily: FF, }} > Question {qIdx + 1} of 10 </div> </div> <div style={cardStyle({ padding: "36px 24px", textAlign: "center", })} > <h2 style={{ fontSize: "clamp(17px, 4.5vw, 22px)", fontWeight: 700, margin: "0 0 14px", lineHeight: 1.35, fontFamily: FF, color: colors.text, }} > {q.text} </h2> <div style={{ display: "inline-flex", alignItems: "center", gap: 6, background: colors.accent + "12", border: "1px solid " + colors.accent + "28", borderRadius: 20, padding: "6px 14px", marginBottom: 32, }} > <span style={{ fontSize: 13 }}>💡</span> <span style={{ color: colors.muted, fontSize: 13, fontFamily: FF, fontStyle: "italic", }} > {q.why} </span> </div> <div style={{ display: "flex", flexDirection: "column", gap: 10, textAlign: "left", }} > {opts.map(({ val, icon, color }) => { const isSel = answers[qIdx] === val const isHov = hoveredAnswer === qIdx + "-" + val return ( <button key={val} onClick={() => pickAnswer(qIdx, val)} onMouseEnter={() => setHoveredAnswer(qIdx + "-" + val) } onMouseLeave={() => setHoveredAnswer(null) } style={{ background: isSel ? color + "18" : isHov ? color + "0C" : "transparent", border: "2px solid " + (isSel ? color : isHov ? color + "66" : colors.cardBorder), borderRadius: 12, padding: "16px 20px", cursor: "pointer", display: "flex", alignItems: "center", gap: 14, transition: "all 0.16s ease", fontFamily: FF, }} > <div style={{ width: 34, height: 34, borderRadius: "50%", background: color + "20", display: "flex", alignItems: "center", justifyContent: "center", color, fontSize: 15, fontWeight: 700, flexShrink: 0, }} > {icon} </div> <span style={{ color: isSel ? color : colors.text, fontSize: 15, fontWeight: 600, }} > {val} </span> </button> ) })} </div> </div> {step > 2 && ( <button onClick={() => go(step - 1)} style={{ background: "transparent", border: "none", color: colors.muted, fontSize: 13, cursor: "pointer", padding: "8px 0", fontFamily: FF, display: "block", margin: "0 auto", }} > ← Back </button> )} </div> ) } // ── SCREEN: Teaser / Gate ──────────────────────────────── function renderTeaser() { const color = getScoreColor(totalScore, colors) const lbl = getScoreLabel(totalScore) return ( <div> <div style={{ textAlign: "center", marginBottom: 24 }}> <div style={{ fontSize: 13, fontWeight: 600, letterSpacing: "1.2px", textTransform: "uppercase", color: colors.accent, fontFamily: FF, }} > Your Results Are Ready </div> <h2 style={{ fontSize: "clamp(20px, 5vw, 28px)", fontWeight: 800, margin: "8px 0 4px", fontFamily: FF, color: colors.text, }} > Your Full Report Is Ready </h2> <p style={{ color: colors.muted, fontSize: 14, fontFamily: FF, }} > Enter your details below to unlock the complete breakdown. </p> </div> {/* Score ring */} <div style={cardStyle({ textAlign: "center", padding: "36px 24px", background: "linear-gradient(155deg, #111520 0%, #0D1018 100%)", border: "1px solid " + color + "33", })} > <div style={{ display: "flex", justifyContent: "center", marginBottom: 16, }} > <ScoreRing score={totalScore} displayed={displayed} size={180} colors={colors} /> </div> <div style={{ color, fontSize: 20, fontWeight: 700, marginBottom: 4, fontFamily: FF, }} > {lbl} </div> <div style={{ color: colors.muted, fontSize: 13, fontFamily: FF, }} > Overall Website & Systems Score </div> </div> {/* Blurred category breakdown */} <div style={cardStyle({ position: "relative", overflow: "hidden", })} > <div style={{ color: colors.muted, fontSize: 12, fontWeight: 600, letterSpacing: "1px", textTransform: "uppercase", marginBottom: 16, fontFamily: FF, }} > Category Breakdown </div> <div style={{ filter: "blur(5px)", userSelect: "none", pointerEvents: "none", }} > {CATEGORIES.map(({ label: catLabel }, i) => ( <div key={catLabel} style={{ marginBottom: 16 }}> <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 6, }} > <span style={{ color: colors.text, fontSize: 14, fontWeight: 500, fontFamily: FF, }} > {catLabel} </span> <span style={{ color: getScoreColor( catScores[i], colors ), fontSize: 14, fontWeight: 700, fontFamily: FF, }} > {catScores[i]}% </span> </div> <div style={{ height: 8, background: colors.subtle, borderRadius: 4, overflow: "hidden", }} > <div style={{ height: "100%", width: catScores[i] + "%", background: getScoreColor( catScores[i], colors ), borderRadius: 4, }} /> </div> </div> ))} </div> {/* Lock overlay */} <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "linear-gradient(160deg, rgba(8,9,13,0.75) 0%, rgba(15,17,23,0.75) 100%)", backdropFilter: "blur(2px)", }} > <div style={{ textAlign: "center", padding: 20 }} > <div style={{ fontSize: 32, marginBottom: 8, }} > 🔒 </div> <div style={{ color: colors.text, fontWeight: 700, fontSize: 15, fontFamily: FF, }} > Unlock Your Full Breakdown </div> <div style={{ color: colors.muted, fontSize: 13, marginTop: 4, fontFamily: FF, }} > Every issue identified. Every fix listed. </div> </div> </div> </div> {/* Blurred revenue */} <div style={cardStyle({ textAlign: "center", position: "relative", overflow: "hidden", padding: "28px 24px", })} > <div style={{ color: colors.muted, fontSize: 12, fontWeight: 600, letterSpacing: "1px", textTransform: "uppercase", marginBottom: 10, fontFamily: FF, }} > Estimated Revenue Leaking </div> <div style={{ fontSize: 42, fontWeight: 800, color: colors.red, filter: "blur(7px)", userSelect: "none", fontFamily: FF, lineHeight: 1, marginBottom: 8, }} > ${fmtMoney(revenueLost)}/mo </div> <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", }} > <div style={{ background: colors.red + "1A", border: "1px solid " + colors.red + "55", borderRadius: 10, padding: "10px 20px", }} > <span style={{ color: colors.red, fontWeight: 700, fontSize: 15, fontFamily: FF, }} > You're leaving an estimated $█,███/month on the table </span> </div> </div> </div> {/* Gate form */} <div style={cardStyle({ border: "1px solid " + colors.accent + "44", background: "linear-gradient(155deg, " + colors.accent + "0A 0%, #0F1117 100%)", })} > <h3 style={{ fontSize: 20, fontWeight: 700, margin: "0 0 8px", textAlign: "center", fontFamily: FF, color: colors.text, }} > Unlock Your Full Results </h3> <p style={{ color: colors.muted, fontSize: 14, textAlign: "center", margin: "0 0 24px", lineHeight: 1.6, fontFamily: FF, }} > Enter your details below to unlock your complete breakdown with specific fixes and revenue impact. </p> <form onSubmit={submitForm} noValidate> {[ { key: "name", label: "Your Name", type: "text", ph: "Dr. Jane Smith", }, { key: "clinicName", label: "Clinic Name", type: "text", ph: "Sunshine Dental", }, { key: "email", label: "Email Address", type: "email", ph: "jane@clinic.com.au", }, { key: "phone", label: "Phone Number", type: "tel", ph: "0400 000 000", }, ].map(({ key, label, type, ph }) => ( <div key={key} style={{ marginBottom: 14 }}> <label style={{ display: "block", color: colors.muted, fontSize: 13, fontWeight: 500, marginBottom: 6, fontFamily: FF, }} > {label} </label> <input type={type} placeholder={ph} value={formData[key]} onChange={(e) => { setFormData((p) => ({ ...p, [key]: e.target.value, })) if (formErrors[key]) setFormErrors((p) => ({ ...p, [key]: null, })) }} style={{ background: "#08090D", border: "1px solid " + (formErrors[key] ? colors.red : colors.cardBorder), borderRadius: 10, padding: "14px 16px", fontSize: 15, color: colors.text, width: "100%", fontFamily: FF, boxSizing: "border-box", outline: "none", }} /> {formErrors[key] && ( <div style={{ color: colors.red, fontSize: 12, marginTop: 3, fontFamily: FF, }} > {formErrors[key]} </div> )} </div> ))} <button type="submit" style={primaryBtn({ fontSize: 17, padding: 18, marginTop: 8, })} > Unlock My Full Results → </button> <p style={{ color: colors.muted, fontSize: 12, textAlign: "center", margin: "12px 0 0", fontFamily: FF, }} > 🔒 We'll never spam you. Your results are sent instantly. </p> </form> </div> </div> ) } // ── SCREEN: Full results ───────────────────────────────── function renderResults() { const color = getScoreColor(totalScore, colors) const lbl = getScoreLabel(totalScore) const clinicName = formData.clinicName || "Your Clinic" return ( <div> {/* Brand header */} <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 20, }} > <div style={{ width: 7, height: 7, borderRadius: "50%", background: colors.accent, }} /> <span style={{ color: colors.accent, fontSize: 12, fontWeight: 700, letterSpacing: "1.2px", textTransform: "uppercase", fontFamily: FF, }} > Newlead Digital — Full Report </span> </div> <h2 style={{ fontSize: "clamp(20px, 5vw, 28px)", fontWeight: 800, margin: "0 0 4px", fontFamily: FF, color: colors.text, letterSpacing: "-0.3px", }} > {clinicName}'s Diagnostic Report </h2> <p style={{ color: colors.muted, fontSize: 14, margin: "0 0 20px", fontFamily: FF, lineHeight: 1.6, }} > Here's exactly where you're leaking revenue — and how to fix it. </p> {/* Score ring */} <div style={cardStyle({ textAlign: "center", padding: "36px 24px", background: "linear-gradient(155deg, #111520 0%, #0D1018 100%)", border: "1px solid " + color + "33", })} > <div style={{ display: "flex", justifyContent: "center", marginBottom: 16, }} > <ScoreRing score={totalScore} displayed={displayed} size={200} colors={colors} /> </div> <div style={{ color, fontSize: 22, fontWeight: 700, marginBottom: 4, fontFamily: FF, }} > {lbl} </div> <div style={{ color: colors.muted, fontSize: 13, fontFamily: FF, }} > Overall score for {clinicName} </div> </div> {/* Category breakdowns */} {CATEGORIES.map(({ label: catLabel, indices }, catIdx) => { const cScore = catScores[catIdx] return ( <div key={catLabel} style={cardStyle({ padding: 24 })} > <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 4, }} > <h3 style={{ fontSize: 17, fontWeight: 700, margin: 0, fontFamily: FF, color: colors.text, }} > {catLabel} </h3> <ScoreBadge score={cScore} colors={colors} /> </div> <AnimatedBar score={cScore} animate={barsGo} colors={colors} delay={catIdx * 180} /> {indices.map((qIdx) => { const ans = answers[qIdx] const q = QUESTIONS[qIdx] const isYes = ans === "YES" const isUnsure = ans === "NOT SURE" return ( <div key={qIdx} style={{ borderTop: "1px solid " + colors.cardBorder, paddingTop: 16, marginTop: 16, }} > {isYes ? ( <div style={{ display: "flex", gap: 12, alignItems: "flex-start", }} > <div style={{ width: 28, height: 28, borderRadius: "50%", background: colors.green + "20", display: "flex", alignItems: "center", justifyContent: "center", color: colors.green, fontSize: 14, fontWeight: 700, flexShrink: 0, }} > ✓ </div> <div> <div style={{ color: colors.text, fontSize: 14, fontWeight: 600, marginBottom: 3, lineHeight: 1.4, fontFamily: FF, }} > {q.text} </div> <div style={{ color: colors.green, fontSize: 13, fontFamily: FF, }} > You're ahead of most clinics here </div> </div> </div> ) : ( <div> <div style={{ display: "flex", gap: 12, alignItems: "flex-start", marginBottom: 12, }} > <div style={{ width: 28, height: 28, borderRadius: "50%", background: isUnsure ? colors.orange + "20" : colors.red + "20", display: "flex", alignItems: "center", justifyContent: "center", color: isUnsure ? colors.orange : colors.red, fontSize: 13, fontWeight: 700, flexShrink: 0, }} > {isUnsure ? "?" : "✗"} </div> <div> <div style={{ color: colors.text, fontSize: 14, fontWeight: 600, marginBottom: 5, lineHeight: 1.4, fontFamily: FF, }} > {q.text} </div> <div style={{ color: isUnsure ? colors.orange : colors.red, fontSize: 13, lineHeight: 1.55, fontFamily: FF, }} > {isUnsure ? "⚠️ Unclear — if you're not sure, it probably isn't set up properly. " + q.problem : "⚠️ " + q.problem} </div> </div> </div> <div style={{ background: colors.accent + "12", border: "1px solid " + colors.accent + "33", borderRadius: 10, padding: "10px 14px", fontSize: 13, color: colors.accent, marginLeft: 40, fontFamily: FF, lineHeight: 1.5, }} > {q.fix} </div> <div style={{ marginLeft: 40, marginTop: 8, fontSize: 12, color: colors.muted, fontFamily: FF, }} > 💸 Estimated impact:{" "} <span style={{ color: colors.red, fontWeight: 600, }} > $ {fmtMoney(q.impact)} /month </span> </div> </div> )} </div> ) })} </div> ) })} {/* Revenue total */} <div style={cardStyle({ textAlign: "center", background: "linear-gradient(155deg, #1A0A0A 0%, #0F0808 100%)", border: "1px solid " + colors.red + "44", padding: "36px 24px", })} > <div style={{ color: colors.muted, fontSize: 12, fontWeight: 600, letterSpacing: "1.2px", textTransform: "uppercase", marginBottom: 10, fontFamily: FF, }} > Revenue Leaking Every Month </div> {revenueLost > 0 ? ( <div> <div style={{ fontSize: "clamp(38px, 9vw, 56px)", fontWeight: 800, color: colors.red, lineHeight: 1, marginBottom: 10, fontFamily: FF, letterSpacing: "-1px", }} > ${fmtMoney(revenueLost)}/mo </div> <div style={{ color: colors.muted, fontSize: 15, fontFamily: FF, }} > That's{" "} <span style={{ color: colors.red, fontWeight: 700, }} > ${fmtMoney(revenueLost * 12)}/year </span>{" "} {clinicName} is leaving on the table. </div> </div> ) : ( <div style={{ color: colors.green, fontSize: 18, fontWeight: 700, fontFamily: FF, }} > 🎉 Impressive! Your systems are well-optimised. <div style={{ color: colors.muted, fontSize: 14, fontWeight: 400, marginTop: 8, }} > Let's make sure you maintain this edge. </div> </div> )} </div> {/* CTA */} <div style={cardStyle({ textAlign: "center", background: "linear-gradient(155deg, " + colors.accent + "10 0%, #0F1117 100%)", border: "1px solid " + colors.accent + "44", padding: "36px 24px", })} > <div style={{ fontSize: 36, marginBottom: 16, lineHeight: 1, }} > 🚀 </div> <h3 style={{ fontSize: "clamp(20px, 5vw, 28px)", fontWeight: 800, margin: "0 0 14px", lineHeight: 1.25, letterSpacing: "-0.3px", fontFamily: FF, color: colors.text, }} > Want Us to Plug These Leaks in 14 Days? </h3> <p style={{ color: colors.muted, fontSize: 15, lineHeight: 1.65, margin: "0 0 28px", fontFamily: FF, }} > Book a free 15-minute discovery call. We'll walk through your results and show you exactly how to fix each issue — fast. </p> <a href={resolvedBookingUrl} target="_blank" rel="noopener noreferrer" style={primaryBtn({ fontSize: 17, padding: 18 })} > Book My Free Discovery Call → </a> <div style={{ marginTop: 20, padding: "14px 18px", background: colors.green + "10", border: "1px solid " + colors.green + "33", borderRadius: 12, color: colors.green, fontSize: 13, lineHeight: 1.55, fontFamily: FF, }} > 🛡️ If we don't recover at least 2x your investment in 90 days, we work for free until we do. </div> </div> <div style={{ textAlign: "center", padding: "16px 0 8px", color: colors.muted, fontSize: 12, fontFamily: FF, }} > Powered by{" "} <span style={{ color: colors.accent, fontWeight: 600 }}> Newlead Digital </span> </div> </div> ) } // ── Render ─────────────────────────────────────────────── function renderStep() { if (step === 0) return renderWelcome() if (step === 1) return renderClinicType() if (step === 12) return renderTeaser() if (step === 13) return renderResults() return renderQuestion(step - 2) } return ( <div style={{ fontFamily: FF, background: colors.bg, minHeight: "100vh", color: colors.text, display: "flex", flexDirection: "column", alignItems: "center", boxSizing: "border-box", width: "100%", }} > {/* Progress bar */} {showProgress && ( <div style={{ width: "100%", maxWidth: 660, padding: "18px 20px 0", }} > <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8, }} > <span style={{ color: colors.muted, fontSize: 12, fontFamily: FF, }} > {step === 12 ? "All done!" : "Step " + step + " of 11"} </span> <span style={{ color: colors.accent, fontSize: 12, fontWeight: 600, fontFamily: FF, }} > {Math.round(progressPct)}% </span> </div> <div style={{ height: 4, background: colors.cardBorder, borderRadius: 2, overflow: "hidden", }} > <div style={{ height: "100%", width: progressPct + "%", background: "linear-gradient(90deg, " + colors.accent + ", " + colors.green + ")", borderRadius: 2, transition: "width 0.6s cubic-bezier(0.16, 1, 0.3, 1)", }} /> </div> </div> )} {/* Main content */} <div style={{ width: "100%", maxWidth: 660, padding: "24px 20px 72px", opacity: visible ? 1 : 0, transform: visible ? "translateY(0px)" : "translateY(14px)", transition: "opacity 0.28s ease, transform 0.28s ease", }} > {renderStep()} </div> </div> ) } // Framer property controls addPropertyControls(ClinicWebsiteGrader, { bookingUrl: { type: ControlType.String, title: "Booking URL", defaultValue: "https://calendly.com/newlead-digital", placeholder: "https://calendly.com/your-link", }, accentColor: { type: ControlType.Color, title: "Accent Color", defaultValue: "#4A9EFF", }, })
Book a call
Not a fan of forms?
Let's talk instead
Book your free 15 minute audit to find out how we can help your business.
FAQ’s
Still got questions?
Answers to common questions about our services, processes, and what sets us apart.
What types of businesses do you work with?
How quickly can we expect results?
How does your “20+ booked appointments” guarantee work?
How do we get started?
Do you manage the ads for us?
What if we’ve never run ads before?
Do we need any software or tools?
What does your process look like?
How much time will this require from us?
What makes Newlead Digital different from other agencies?
What types of businesses do you work with?
How quickly can we expect results?
How does your “20+ booked appointments” guarantee work?
How do we get started?
Do you manage the ads for us?
What if we’ve never run ads before?
Do we need any software or tools?
What does your process look like?
How much time will this require from us?
What makes Newlead Digital different from other agencies?
What types of businesses do you work with?
How quickly can we expect results?
How does your “20+ booked appointments” guarantee work?
How do we get started?
Do you manage the ads for us?
What if we’ve never run ads before?
Do we need any software or tools?
What does your process look like?
How much time will this require from us?
What makes Newlead Digital different from other agencies?
Still Have a Question