import * as React from "react"
import { addPropertyControls, ControlType } from "framer"
/**
* NORTHARIS FASTTRACK
* Single-file Framer code component.
*
* Setup in Framer:
* 1. Assets > Code > New file > paste this whole file
* 2. Drag the component onto the /fasttrack page, set width 100% and height auto
* 3. In the component props panel, paste your Power Automate HTTP trigger URL
* into "Webhook URL" to enable lead capture and abandoned cart events
*
* Events posted to the webhook: lead_captured, step_reached,
* fasttrack_secured_card, fasttrack_secured_po. Flag any lead_captured
* with no secured event inside your follow-up window and send the
* "you left something in the cart" email.
*
* @framerSupportedLayoutWidth any
* @framerSupportedLayoutHeight any
*/
/* ================= DATA ================= */
const TIERS = [
{ id: "t1", name: "Starting a business", sub: "10 users or less", price: 3000, label: "CAD $3,000", mode: "fixed" },
{ id: "t2", name: "11 to 250 users", sub: "Small and growing teams", price: 3500, label: "CAD $3,500", mode: "starting" },
{ id: "t3", name: "251 to 500 users", sub: "Established mid-market", price: 5500, label: "CAD $5,500", mode: "starting" },
{ id: "t4", name: "501 to 1,000 users", sub: "Larger environments", price: 7500, label: "CAD $7,500", mode: "starting" },
{ id: "t5", name: "1,001 to 2,000 users", sub: "Multi-site or multi-domain", price: 9500, label: "CAD $9,500", mode: "starting" },
{ id: "t6", name: "2,001 to 5,000 users", sub: "Enterprise scale", price: 14500, label: "CAD $14,500", mode: "starting" },
{ id: "t7", name: "5,001+ users", sub: "Custom-scoped engagement", price: 18500, label: "From CAD $18,500", mode: "custom" },
]
const OUTCOMES = [
{
id: "launch",
name: "Business Launch FastTrack",
launchOnly: true,
tag: "Fixed price CAD $3,000",
hook: "Starting a business and need an IT partner to help you grow?",
desc: "Set up a clean Microsoft tenant, domain, email, baseline security, Teams, OneDrive, SharePoint basics, and admin handoff for up to 10 users.",
included: ["Kickoff call", "Microsoft 365 tenant setup or review", "Domain connection guidance", "DNS record configuration guidance", "Email setup for up to 10 users", "Basic MFA configuration", "Baseline admin account review", "Basic Teams setup", "Basic OneDrive and SharePoint setup", "Basic security recommendations", "Admin handoff document", "Final handoff call"],
deliverables: ["Tenant setup summary", "User and mailbox setup summary", "Basic security checklist", "Admin handoff guide", "Recommended next steps"],
exclusions: ["Licensing costs", "Domain purchase costs", "Hardware purchase", "Device imaging", "Full Intune deployment", "Email migration from another platform", "Data migration", "Advanced SharePoint architecture", "Custom Teams structure", "Ongoing helpdesk", "Managed services", "Cybersecurity monitoring", "Backup setup", "Third-party app setup", "Website hosting", "Line-of-business application support"],
terms: "Business Launch FastTrack is available only for organizations with 10 users or less. Additional users, migrations, advanced security configuration, device deployment, data migration, application setup, and ongoing support are billed separately. Once the organization exceeds 10 users, Northaris may recommend a support, advisory, or managed services model under a separate agreement.",
},
{
id: "support",
name: "Reduce IT Support Demand",
hook: "We need fewer recurring IT issues.",
desc: "Reduce avoidable tickets caused by access issues, device problems, app failures, policy conflicts, and inconsistent Microsoft configuration.",
included: ["Ticket driver discovery session", "Endpoint policy review", "Compliance policy review", "App deployment review", "Device enrollment review", "Conditional Access impact review", "Microsoft 365 support friction review", "Recurring issue pattern analysis", "Remediation roadmap"],
deliverables: ["Support demand findings report", "Top recurring ticket drivers", "Endpoint and access issue summary", "Quick-win remediation list", "30 / 60 / 90 day reduction roadmap", "Executive scorecard"],
exclusions: ["Helpdesk operation", "Ticket queue management", "End-user support", "Live troubleshooting for individual users", "Policy remediation", "App packaging", "Device rebuilds", "Vendor escalation", "SLA-backed support", "Managed services"],
terms: "This FastTrack identifies likely causes of recurring IT support demand. It does not guarantee a specific ticket reduction percentage unless a separate remediation and operating agreement is executed.",
},
{
id: "devices",
name: "Accelerate Device Deployment",
hook: "We need devices deployed faster.",
desc: "Reduce imaging, manual setup, onboarding delays, refresh complexity, and technician-heavy deployment work.",
included: ["Device deployment process review", "Autopilot readiness review", "Intune enrollment review", "App deployment review", "Device compliance review", "Windows 11 readiness considerations", "Build and handoff process review", "Deployment blocker analysis", "Remediation roadmap"],
deliverables: ["Device deployment assessment", "Autopilot and Intune readiness findings", "Deployment bottleneck report", "Recommended deployment model", "30 / 60 / 90 day modernization roadmap", "Executive scorecard"],
exclusions: ["Full Autopilot deployment", "Device imaging", "Application packaging", "Driver packaging", "Hands-on device provisioning", "Hardware procurement", "Windows migration execution", "Field services", "End-user scheduling", "Factory provisioning", "White-glove deployment execution"],
terms: "This FastTrack evaluates device deployment readiness and provides a modernization plan. Actual deployment, packaging, migration, and device rollout work require a separate implementation agreement.",
},
{
id: "security",
name: "Strengthen Microsoft Security",
hook: "We need to close security gaps.",
desc: "Improve identity, endpoint, admin, data, and access controls across Microsoft 365.",
included: ["Entra ID security review", "MFA and Conditional Access review", "Admin role review", "Secure Score review", "Defender capability review", "Endpoint security posture review", "Guest access review", "Data exposure risk review", "Security roadmap"],
deliverables: ["Microsoft security posture report", "Identity and access findings", "Admin risk findings", "Endpoint protection findings", "Priority remediation roadmap", "Executive risk scorecard"],
exclusions: ["Penetration testing", "Red team testing", "Incident response", "Security monitoring", "SIEM deployment", "Defender tuning implementation", "Conditional Access deployment", "Privileged access implementation", "Compliance certification", "Legal or audit opinion", "Cyber insurance attestation"],
terms: "This FastTrack is a Microsoft security posture assessment. It is not a penetration test, audit, certification, breach investigation, or legal compliance opinion.",
},
{
id: "byod",
name: "Enable Secure BYOD",
hook: "We need secure access from personal devices.",
desc: "Support Windows and macOS BYOD without giving unmanaged devices full access to company data.",
included: ["BYOD access model review", "Windows and macOS access review", "Conditional Access review", "Browser-based access strategy", "Defender for Cloud Apps / MCAS review", "MAM and app protection review", "SharePoint and OneDrive access review", "User impact review", "BYOD roadmap"],
deliverables: ["BYOD security assessment", "Recommended access model", "Browser vs app access decision matrix", "User impact summary", "Policy roadmap", "Executive scorecard"],
exclusions: ["Full BYOD implementation", "Policy deployment", "End-user communication rollout", "Device enrollment campaign", "App protection policy implementation", "Defender for Cloud Apps implementation", "Conditional Access enforcement", "Helpdesk support during rollout", "Legal HR policy drafting"],
terms: "This FastTrack provides a secure BYOD strategy and implementation roadmap. Enforcement, rollout, communications, and support require a separate implementation agreement.",
},
{
id: "waste",
name: "Reduce Microsoft Waste",
hook: "We need more value from Microsoft.",
desc: "Find unused licensing, overlapping tools, underused E3/E5 capabilities, and areas where spend is not creating value.",
included: ["Microsoft license utilization review", "E3 / E5 capability mapping", "Overlapping tool review", "Security stack overlap review", "User adoption signal review", "Unused workload review", "Cost reduction opportunities", "Optimization roadmap"],
deliverables: ["Microsoft value assessment", "License optimization summary", "Tool overlap findings", "Consolidation opportunities", "Savings opportunity roadmap", "Executive scorecard"],
exclusions: ["Licensing procurement", "License cancellation", "Contract negotiation", "CSP resale", "EA renewal negotiation", "SAM audit defense", "Vendor negotiation", "Guaranteed savings", "Implementation of consolidation recommendations"],
terms: "Savings estimates are directional and based on available data at the time of assessment. Actual savings depend on licensing agreements, user adoption, renewal timing, business decisions, and implementation of recommendations.",
},
{
id: "copilot",
name: "Prepare for Copilot",
hook: "We need to prepare for AI.",
desc: "Get ready for Copilot with the right governance, permissions, security, adoption, and licensing strategy.",
included: ["Copilot readiness review", "Identity and access review", "SharePoint and OneDrive exposure review", "Teams governance review", "Sensitivity label readiness review", "Licensing readiness review", "User readiness considerations", "Adoption risk review", "AI governance roadmap"],
deliverables: ["Copilot readiness assessment", "Data exposure findings", "Governance recommendations", "Licensing readiness summary", "Adoption roadmap", "Executive scorecard"],
exclusions: ["Copilot license purchase", "Copilot deployment", "Full SharePoint cleanup", "Permission remediation", "Sensitivity label implementation", "DLP implementation", "End-user training delivery", "AI policy legal review", "Custom Copilot agent build", "Power Platform development"],
terms: "This FastTrack evaluates readiness for Copilot. It does not guarantee Copilot adoption, productivity gains, data cleanup completion, or AI governance compliance unless implemented under a separate agreement.",
},
{
id: "visibility",
name: "Improve Visibility",
hook: "We need better visibility.",
desc: "Give IT and leadership a clearer view of risk, compliance, endpoint health, readiness, and priorities.",
included: ["Current reporting review", "Intune visibility review", "Security visibility review", "Device health visibility review", "Compliance reporting review", "Executive reporting needs review", "Dashboard gap analysis", "Visibility roadmap"],
deliverables: ["Visibility gap assessment", "Reporting recommendations", "Executive scorecard model", "Metrics and KPI recommendations", "Dashboard roadmap", "Priority remediation list"],
exclusions: ["Dashboard build", "Power BI development", "Data warehouse build", "Custom reporting automation", "SIEM implementation", "Real-time monitoring", "Managed reporting service", "Ongoing monthly reporting"],
terms: "This FastTrack defines what should be measured and how visibility can be improved. Dashboard creation, automation, and ongoing reporting require a separate implementation agreement.",
},
{
id: "resilience",
name: "Increase Resilience",
hook: "We need to reduce downtime risk.",
desc: "Keep users working through outages, access issues, device failures, and continuity events.",
included: ["Business continuity discovery", "Critical user group review", "Endpoint recovery review", "Identity access continuity review", "Windows 365 / Cloud PC fit review", "AVD consideration review", "Access failure scenario review", "Recovery roadmap"],
deliverables: ["Workforce resilience assessment", "Critical user continuity model", "Cloud PC / AVD fit summary", "Recovery risk findings", "Resilience roadmap", "Executive scorecard"],
exclusions: ["Full BCDR plan", "Disaster recovery implementation", "Backup implementation", "Windows 365 deployment", "AVD deployment", "Cloud infrastructure build", "Incident response", "Tabletop exercise", "Business impact analysis", "SLA-backed recovery service"],
terms: "This FastTrack provides a technology resilience assessment and roadmap. It is not a full business continuity plan, disaster recovery implementation, backup service, or guaranteed recovery solution.",
},
]
const DATES = ["ASAP", "Within 2 weeks", "Within 30 days", "This quarter"]
const GIVES = ["Kickoff call", "Required information checklist", "Guided data collection", "Microsoft environment review based on selected outcomes", "Findings summary", "Executive scorecard", "Remediation roadmap", "Final review call", "Implementation estimate if remediation is required"]
const NOT_GIVES = ["Hands-on remediation or configuration changes", "Migration work", "Licensing or hardware procurement", "End-user, helpdesk, or after-hours support", "Emergency incident response", "Custom development or third-party vendor management", "Legal, compliance, audit, or regulatory certification", "Penetration testing", "Full project management for implementation", "Documentation beyond the listed deliverables"]
const STEPS = ["Your details", "Company size", "Business outcomes", "Start date", "Scope review", "Secure FastTrack"]
const fmt = (n: number) => "CAD $" + n.toLocaleString("en-CA")
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/
/* ================= STYLES ================= */
const CSS = `
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;500;600;700;800&display=swap');
.nft, .nft *{margin:0;padding:0;box-sizing:border-box;font-family:'Manrope',system-ui,sans-serif}
.nft{
/* Northaris Group brand tokens */
--ink:#060828; /* primary brand navy */
--ghost:#F8F8FF; /* primary light */
--grad:linear-gradient(120deg,#B7B3FF 0%,#5C56D8 100%);
--purple:#5C56D8;
--purple-light:#B7B3FF;
--cloud:#EDEFF7;
--smoke:#D3D6E0;
--steel:#BCBFCC;
--space:#9DA2B3;
--graphite:#6E7180;
--arsenic:#40424D;
--phantom:#1E1E24;
--r:20px;
--shadow-1:0 1px 2px rgba(6,8,40,.04), 0 8px 24px rgba(6,8,40,.05);
--shadow-2:0 2px 4px rgba(6,8,40,.05), 0 16px 40px rgba(6,8,40,.09);
background:var(--ghost);color:var(--ink);font-size:16px;line-height:1.6;
-webkit-font-smoothing:antialiased;width:100%;
}
.nft button{cursor:pointer;color:inherit}
.nft .app{display:grid;grid-template-columns:220px minmax(0,1fr) 340px;gap:48px;max-width:1360px;margin:0 auto;padding:64px 40px 96px;align-items:start}
/* ---------- rail: quiet, typographic, no chrome ---------- */
.nft .rail{position:sticky;top:48px;padding-top:8px}
.nft .rail-title{font-size:13px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;color:var(--space);margin-bottom:28px}
.nft .rail-step{display:flex;align-items:center;gap:14px;width:100%;text-align:left;padding:10px 0;border:0;background:transparent;color:var(--space);font-size:16px;font-weight:600;transition:color .18s;letter-spacing:-.01em}
.nft .rail-step:hover:not(:disabled){color:var(--graphite)}
.nft .rail-step:disabled{opacity:.35;cursor:default}
.nft .rail-step.active{color:var(--ink);font-weight:800}
.nft .dot{flex:none;width:28px;height:28px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;background:var(--cloud);color:var(--space);transition:background .18s,color .18s}
.nft .rail-step.active .dot{background:var(--ink);color:#fff}
.nft .rail-step.done .dot{background:var(--grad);color:#fff}
.nft .rail-foot{margin-top:36px;font-size:14px;color:var(--space);line-height:1.6;max-width:180px}
/* ---------- workspace ---------- */
.nft .work{min-width:0;animation:nftFade .4s cubic-bezier(.16,1,.3,1)}
@keyframes nftFade{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.nft .eyebrow{font-size:13px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:16px}
.nft h1{font-size:clamp(38px,4.2vw,56px);font-weight:800;letter-spacing:-.03em;line-height:1.04;max-width:640px}
.nft h2{font-size:24px;font-weight:800;letter-spacing:-.02em}
.nft .lede{font-size:18px;color:var(--graphite);margin:20px 0 44px;max-width:560px;font-weight:500}
.nft .card{background:#fff;border-radius:var(--r);padding:32px;box-shadow:var(--shadow-1)}
/* ---------- lead capture ---------- */
.nft .lead-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}
.nft .field{display:flex;flex-direction:column;gap:8px}
.nft .field label{font-size:14px;font-weight:700;color:var(--ink)}
.nft .field label em{font-style:normal;font-weight:500;color:var(--space)}
.nft .field input{font-size:16px;font-weight:600;color:var(--ink);background:var(--cloud);border:0;border-radius:14px;padding:16px 18px;transition:background .18s,box-shadow .18s}
.nft .field input::placeholder{color:var(--space);font-weight:500}
.nft .field input:focus{outline:0;background:#fff;box-shadow:0 0 0 4px rgba(92,86,216,.16), var(--shadow-1)}
.nft .field input.bad{box-shadow:0 0 0 4px rgba(192,82,82,.14);background:#fff}
.nft .field .err{font-size:13px;color:#B14A4A;font-weight:600}
/* ---------- selectable surfaces: solid fill states, no outlines ---------- */
.nft .size-grid{display:grid;gap:14px}
.nft .size-opt{display:flex;align-items:center;justify-content:space-between;gap:20px;background:#fff;border:0;border-radius:var(--r);padding:24px 28px;text-align:left;box-shadow:var(--shadow-1);transition:transform .18s,box-shadow .18s,background .18s,color .18s}
.nft .size-opt:hover{transform:translateY(-2px);box-shadow:var(--shadow-2)}
.nft .size-opt.sel{background:var(--ink);color:#fff}
.nft .size-name{font-weight:800;font-size:18px;letter-spacing:-.01em}
.nft .size-sub{font-size:14px;color:var(--graphite);margin-top:3px;display:block;font-weight:500}
.nft .size-opt.sel .size-sub{color:var(--steel)}
.nft .size-price{font-weight:800;font-size:18px;white-space:nowrap;letter-spacing:-.01em}
.nft .size-price small{display:block;font-weight:600;font-size:12.5px;color:var(--space);text-align:right;margin-top:2px}
.nft .size-opt.sel .size-price small{color:var(--purple-light)}
.nft .oc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));gap:16px}
.nft .oc{position:relative;text-align:left;background:#fff;border:0;border-radius:var(--r);padding:28px;box-shadow:var(--shadow-1);transition:transform .18s,box-shadow .18s,background .18s,color .18s;display:flex;flex-direction:column;gap:10px}
.nft .oc:hover:not(:disabled){transform:translateY(-2px);box-shadow:var(--shadow-2)}
.nft .oc.sel{background:var(--ink);color:#fff}
.nft .oc:disabled{opacity:.4;cursor:default}
.nft .oc-hook{font-weight:800;font-size:18px;line-height:1.25;padding-right:34px;letter-spacing:-.015em}
.nft .oc-desc{font-size:14.5px;color:var(--graphite);font-weight:500}
.nft .oc.sel .oc-desc{color:var(--steel)}
.nft .oc-tag{font-size:12.5px;font-weight:800;letter-spacing:.1em;text-transform:uppercase;background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.nft .oc.sel .oc-tag{background:none;-webkit-text-fill-color:var(--purple-light);color:var(--purple-light)}
.nft .oc-check{position:absolute;top:22px;right:22px;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:12px;background:var(--cloud);color:transparent;transition:background .18s,color .18s}
.nft .oc.sel .oc-check{background:var(--grad);color:#fff}
.nft .notice{margin-top:20px;border-radius:16px;padding:16px 22px;font-size:15px;font-weight:600;background:var(--ink);color:var(--purple-light)}
.nft .notice.warn{background:var(--cloud);color:var(--arsenic)}
.nft .date-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px}
.nft .date-opt{background:#fff;border:0;border-radius:var(--r);padding:26px 24px;font-weight:800;font-size:17px;text-align:left;letter-spacing:-.01em;box-shadow:var(--shadow-1);transition:transform .18s,box-shadow .18s,background .18s,color .18s}
.nft .date-opt:hover{transform:translateY(-2px);box-shadow:var(--shadow-2)}
.nft .date-opt.sel{background:var(--ink);color:#fff}
/* ---------- scope review ---------- */
.nft .scope-cols{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}
.nft .scope-cols h2{margin-bottom:16px;font-size:20px}
.nft ul{list-style:none}
.nft .li{position:relative;padding:5px 0 5px 26px;font-size:15px;color:var(--graphite);font-weight:500}
.nft .li::before{position:absolute;left:0;font-weight:800}
.nft .li.yes::before{content:'\\2713';color:var(--purple)}
.nft .li.no::before{content:'\\2715';color:var(--space)}
.nft .li.del::before{content:'\\25B8';color:var(--purple);left:2px}
.nft .acc{border-radius:var(--r);background:#fff;margin-bottom:14px;box-shadow:var(--shadow-1);overflow:hidden;transition:box-shadow .18s}
.nft .acc:hover{box-shadow:var(--shadow-2)}
.nft .acc-head{width:100%;display:flex;align-items:center;justify-content:space-between;gap:14px;background:transparent;border:0;padding:26px 30px;text-align:left;font-size:19px;font-weight:800;color:var(--ink);letter-spacing:-.015em}
.nft .acc-head span{transition:transform .25s;color:var(--space);font-size:14px}
.nft .acc.open .acc-head span{transform:rotate(180deg)}
.nft .acc-body{padding:0 30px 28px}
.nft .acc-body h3{font-size:13px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--space);margin:22px 0 8px}
.nft .terms-box{font-size:14.5px;color:var(--arsenic);background:var(--cloud);border-radius:14px;padding:18px 20px;margin-top:20px;font-weight:500;line-height:1.6}
.nft .packet-row{display:flex;gap:12px;flex-wrap:wrap;margin:24px 0}
/* ---------- buttons: solid, weighty, no outlines ---------- */
.nft .btn{border:0;border-radius:14px;padding:16px 28px;font-size:16px;font-weight:700;letter-spacing:-.01em;transition:transform .18s,box-shadow .18s,background .18s}
.nft .btn:active{transform:scale(.98)}
.nft .btn-primary{background:var(--ink);color:#fff;box-shadow:var(--shadow-1)}
.nft .btn-primary:hover:not(:disabled){background:var(--phantom);transform:translateY(-1px);box-shadow:var(--shadow-2)}
.nft .btn-primary:disabled{background:var(--smoke);color:var(--space);box-shadow:none;cursor:default}
.nft .btn-ghost{background:var(--cloud);color:var(--ink)}
.nft .btn-ghost:hover{background:var(--smoke)}
.nft .ack{display:flex;gap:16px;align-items:flex-start;background:#fff;border-radius:var(--r);padding:24px 28px;margin-top:8px;cursor:pointer;box-shadow:var(--shadow-1);transition:background .18s,color .18s}
.nft .ack.on{background:var(--ink);color:#fff}
.nft .ack input{margin-top:4px;width:18px;height:18px;accent-color:var(--purple)}
.nft .ack p{font-size:15.5px;font-weight:600}
/* ---------- secure ---------- */
.nft .pay-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.nft .pay-card{display:flex;flex-direction:column;gap:16px;padding:36px}
.nft .pay-card h2{font-size:22px}
.nft .pay-card p{font-size:15px;color:var(--graphite);flex:1;font-weight:500}
.nft .confirm{margin-top:24px;border-radius:var(--r);background:var(--ink);padding:26px 30px;font-size:16px;font-weight:600;color:#fff;animation:nftFade .4s cubic-bezier(.16,1,.3,1);line-height:1.6}
.nft .step-nav{display:flex;justify-content:space-between;gap:12px;margin-top:36px}
/* ---------- live summary: deep navy, spacing over rules ---------- */
.nft .summary{position:sticky;top:48px;background:var(--ink);color:#fff;border-radius:24px;box-shadow:0 24px 64px rgba(6,8,40,.28)}
.nft .sum-in{padding:36px 32px}
.nft .sum-title{font-size:13px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;color:var(--purple-light);margin-bottom:28px}
.nft .sum-row{display:flex;justify-content:space-between;gap:14px;padding:8px 0;font-size:15px}
.nft .sum-row .k{color:var(--space);flex:none;font-weight:500}
.nft .sum-row .v{font-weight:700;text-align:right}
.nft .sum-outs{padding:14px 0 6px}
.nft .sum-outs .k{color:var(--space);font-size:15px;font-weight:500}
.nft .sum-outs ul{margin-top:10px}
.nft .sum-outs li{font-size:15px;font-weight:600;padding:4px 0 4px 18px;position:relative}
.nft .sum-outs li::before{content:'';position:absolute;left:0;top:13px;width:7px;height:7px;border-radius:50%;background:var(--grad)}
.nft .sum-outs li.none{color:var(--graphite);padding-left:0;font-weight:500}
.nft .sum-outs li.none::before{display:none}
.nft .sum-total{display:flex;flex-direction:column;gap:4px;padding:28px 0 8px}
.nft .sum-total .k{font-size:14px;color:var(--space);font-weight:500}
.nft .sum-total .v{font-size:38px;font-weight:800;letter-spacing:-.03em;line-height:1.1;background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.nft .sum-note{font-size:13px;color:var(--graphite);margin-top:10px;line-height:1.6;font-weight:500}
.nft .sum-credit{margin-top:22px;border-radius:16px;padding:18px 20px;background:var(--phantom);font-size:13.5px;color:var(--steel);line-height:1.6;font-weight:500}
.nft .foot{grid-column:1 / -1;font-size:13.5px;color:var(--space);padding-top:48px;line-height:1.7;max-width:900px;font-weight:500}
.nft button:focus-visible{outline:3px solid rgba(92,86,216,.4);outline-offset:3px}
@media (prefers-reduced-motion:reduce){.nft *{animation:none !important;transition:none !important}}
@media (max-width:1120px){
.nft .app{grid-template-columns:200px minmax(0,1fr);gap:36px}
.nft .summary{position:static;grid-column:1 / -1;order:3}
}
@media (max-width:760px){
.nft .app{grid-template-columns:1fr;padding:32px 20px 64px;gap:28px}
.nft .rail{position:static;display:flex;flex-wrap:wrap;gap:8px;padding:0}
.nft .rail-title,.nft .rail-foot,.nft .rail-step .lbl{display:none}
.nft .rail-step{width:auto;padding:4px}
.nft h1{font-size:34px}
.nft .scope-cols,.nft .pay-grid,.nft .lead-grid{grid-template-columns:1fr}
}
`
/* ================= COMPONENT ================= */
export default function NortharisFastTrack(props: { webhookUrl?: string }) {
const webhookUrl = props.webhookUrl || ""
const [step, setStep] = React.useState(1)
const [lead, setLead] = React.useState({ name: "", email: "", company: "", phone: "" })
const [touched, setTouched] = React.useState<{ [k: string]: boolean }>({})
const [tier, setTier] = React.useState<any>(null)
const [outcomes, setOutcomes] = React.useState<any[]>([])
const [start, setStart] = React.useState<string | null>(null)
const [ack, setAck] = React.useState(false)
const [openAcc, setOpenAcc] = React.useState<{ [k: string]: boolean }>({})
const [confirm, setConfirm] = React.useState("")
const leadSent = React.useRef(false)
const leadValid =
lead.name.trim().length > 1 &&
EMAIL_RE.test(lead.email) &&
lead.company.trim().length > 1
/* ----- pricing ----- */
const pricing = React.useMemo(() => {
const n = outcomes.length
if (!tier) return { tier: "\u2013", addon: "\u2013", total: "CAD $0", custom: false }
if (tier.mode === "custom" || n >= 5) {
return {
tier: tier.label,
addon: n >= 5 ? "Custom scope required" : n === 4 ? "+ CAD $5,000" : n === 3 ? "+ CAD $2,500" : "Included in tier",
total: "Custom quote",
custom: true,
}
}
const launchOnly = n === 1 && outcomes[0].id === "launch"
const base = launchOnly ? 3000 : tier.price
let addon = 0
let addonLabel = n === 0 ? "\u2013" : "Included in tier"
if (n === 3) { addon = 2500; addonLabel = "+ CAD $2,500" }
if (n === 4) { addon = 5000; addonLabel = "+ CAD $5,000" }
const suffix = tier.mode === "starting" ? " starting" : ""
return {
tier: tier.label + (tier.mode === "starting" ? " starting" : " fixed"),
addon: addonLabel,
total: fmt(base + addon) + suffix,
custom: false,
}
}, [tier, outcomes])
/* ----- abandoned cart tracking ----- */
const track = React.useCallback(
(event: string, extra: any = {}) => {
if (!webhookUrl || !leadValid) return
const payload = {
event,
timestamp: new Date().toISOString(),
lead,
companySize: tier ? tier.name : null,
outcomes: outcomes.map((o) => o.name),
preferredStart: start,
estimatedTotal: pricing.total,
acknowledged: ack,
stepReached: step,
...extra,
}
try {
const blob = new Blob([JSON.stringify(payload)], { type: "application/json" })
if (navigator.sendBeacon) navigator.sendBeacon(webhookUrl, blob)
else fetch(webhookUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), keepalive: true }).catch(() => {})
} catch (e) {}
},
[webhookUrl, leadValid, lead, tier, outcomes, start, pricing, ack, step]
)
/* ----- navigation ----- */
const canReview = leadValid && !!tier && outcomes.length > 0 && !!start
const go = (n: number) => {
if (n >= 2 && !leadValid) return
if (n === 5 && !canReview) return
if (n === 6 && !ack) return
setStep(n)
if (n >= 2 && leadSent.current) track("step_reached", { stepReached: n })
if (typeof window !== "undefined") window.scrollTo({ top: 0 })
}
const continueFromLead = () => {
if (!leadValid) return
if (!leadSent.current) {
track("lead_captured", { stepReached: 2 })
leadSent.current = true
}
go(2)
}
const stepEnabled = (n: number) => {
if (n === 1) return true
if (n >= 2 && n <= 4) return leadValid
if (n === 5) return canReview
if (n === 6) return canReview && ack
return false
}
/* ----- selections (single-choice steps auto-advance) ----- */
const advanceTimer = React.useRef<any>(null)
const autoAdvance = (n: number, extra: any = {}) => {
if (advanceTimer.current) clearTimeout(advanceTimer.current)
advanceTimer.current = setTimeout(() => {
setStep(n)
if (leadSent.current) track("step_reached", { stepReached: n, ...extra })
if (typeof window !== "undefined") window.scrollTo({ top: 0 })
}, 350)
}
React.useEffect(() => () => { if (advanceTimer.current) clearTimeout(advanceTimer.current) }, [])
const pickTier = (t: any) => {
setTier(t)
if (t.id !== "t1") setOutcomes((prev) => prev.filter((o) => o.id !== "launch"))
autoAdvance(3, { companySize: t.name })
}
const pickDate = (d: string) => {
setStart(d)
autoAdvance(5, { preferredStart: d })
}
const toggleAck = (checked: boolean) => {
setAck(checked)
if (checked) autoAdvance(6, { acknowledged: true })
else if (advanceTimer.current) clearTimeout(advanceTimer.current)
}
const toggleOutcome = (o: any) => {
setOutcomes((prev) => (prev.some((s) => s.id === o.id) ? prev.filter((s) => s.id !== o.id) : [...prev, o]))
}
/* ----- scope packet ----- */
const packetText = () => {
let t = `NORTHARIS FASTTRACK SCOPE PACKET\nGenerated ${new Date().toLocaleDateString("en-CA")}\n\n`
t += `Prepared for: ${lead.name.trim()}\nCompany: ${lead.company.trim()}\nEmail: ${lead.email.trim()}${lead.phone.trim() ? "\nPhone: " + lead.phone.trim() : ""}\n\n`
t += `Company size: ${tier ? tier.name : "Not selected"}\nTier pricing: ${pricing.tier}\nMulti-outcome: ${pricing.addon}\nPreferred start: ${start || "Not selected"}\nEstimated total: ${pricing.total}\n\n`
t += `WHAT NORTHARIS GIVES YOU\n${GIVES.map((x) => " - " + x).join("\n")}\n\n`
t += `WHAT NORTHARIS DOES NOT GIVE YOU\n${NOT_GIVES.map((x) => " - " + x).join("\n")}\n\n`
outcomes.forEach((o) => {
t += `==============================\n${o.name.toUpperCase()}\n==============================\n`
t += `Included:\n${o.included.map((x: string) => " - " + x).join("\n")}\n`
t += `Deliverables:\n${o.deliverables.map((x: string) => " - " + x).join("\n")}\n`
t += `Exclusions:\n${o.exclusions.map((x: string) => " - " + x).join("\n")}\n`
t += `Terms: ${o.terms}\n\n`
})
t += `COMMERCIAL TERMS\nFastTrack is a fixed-scope assessment and planning engagement unless implementation is specifically purchased. Implementation is not included unless stated in writing. The reports are yours to keep. If your organization proceeds with Northaris for remediation within 30 days of delivery, 50% of the FastTrack fee may be credited toward the approved implementation engagement. The remediation credit applies only to implementation work directly related to the FastTrack findings. The remediation credit cannot be applied to licensing, hardware, third-party tools, managed services, or unrelated work. For purchase order clients, work begins once the PO, signed approval, or mutually accepted written authorization is received.\n`
return t
}
const downloadPacket = () => {
const blob = new Blob([packetText()], { type: "text/plain" })
const a = document.createElement("a")
a.href = URL.createObjectURL(blob)
a.download = "Northaris-FastTrack-Scope-Packet.txt"
a.click()
URL.revokeObjectURL(a.href)
}
const emailPacket = () => {
const body = encodeURIComponent(packetText().slice(0, 1800) + "\n\n[Full packet available as download]")
window.location.href = `mailto:${encodeURIComponent(lead.email.trim())}?subject=${encodeURIComponent("Northaris FastTrack Scope Packet")}&body=${body}`
}
/* ----- step 6 actions ----- */
const secureCard = () => {
track("fasttrack_secured_card")
setConfirm(`Kickoff secured. You will not be charged today. Northaris will contact ${lead.email.trim()} to confirm scheduling, and payment is processed only after the agreed deliverables are sent for review.`)
}
const securePO = () => {
track("fasttrack_secured_po")
setConfirm(`PO package requested. Northaris will send a procurement-ready package to ${lead.email.trim()} with your selected outcomes, scope, exclusions, deliverables, pricing, estimated timeline, and vendor details.`)
}
/* ----- field helper ----- */
const field = (key: keyof typeof lead, label: string, placeholder: string, type: string, autoComplete: string, required: boolean, errMsg?: string) => {
const val = lead[key]
const bad =
required && touched[key] && val !== "" &&
(key === "email" ? !EMAIL_RE.test(val) : val.trim().length < 2)
return (
<div className="field">
<label htmlFor={"nft-" + key}>
{label} {!required && <em>(optional)</em>}
</label>
<input
id={"nft-" + key}
type={type}
autoComplete={autoComplete}
placeholder={placeholder}
value={val}
className={bad ? "bad" : ""}
onChange={(e) => setLead({ ...lead, [key]: e.target.value })}
onBlur={() => setTouched({ ...touched, [key]: true })}
onKeyDown={(e) => { if (e.key === "Enter") continueFromLead() }}
/>
{bad && errMsg && <span className="err">{errMsg}</span>}
</div>
)
}
/* ================= RENDER ================= */
return (
<div className="nft">
<style>{CSS}</style>
<div className="app">
{/* -------- rail -------- */}
<nav className="rail" aria-label="FastTrack steps">
<div className="rail-title">FastTrack</div>
{STEPS.map((label, i) => {
const n = i + 1
return (
<button
key={n}
className={"rail-step" + (step === n ? " active" : "") + (step > n ? " done" : "")}
disabled={!stepEnabled(n)}
onClick={() => go(n)}
>
<span className="dot">{n}</span>
<span className="lbl">{label}</span>
</button>
)
})}
<div className="rail-foot">Fixed scope. Clear exclusions. No hidden assumptions.</div>
</nav>
{/* -------- workspace -------- */}
<main className="work" key={step}>
{step === 1 && (
<section>
<div className="eyebrow">Step 1 of 6</div>
<h1>Buy what your company already knows it needs.</h1>
<p className="lede">No one knows your business more than you do. Tell us who to prepare this for, then build your FastTrack, review the exact scope, and secure the engagement.</p>
<div className="card">
<div className="lead-grid">
{field("name", "Full name", "Jane Rivera", "text", "name", true, "Enter your name.")}
{field("email", "Work email", "jane@company.com", "email", "email", true, "Enter a valid work email.")}
{field("company", "Company", "Company Inc.", "text", "organization", true, "Enter your company name.")}
{field("phone", "Phone", "(905) 555-0100", "tel", "tel", false)}
</div>
</div>
<div className="step-nav">
<span />
<button className="btn btn-primary" disabled={!leadValid} onClick={continueFromLead}>
Continue to company size
</button>
</div>
</section>
)}
{step === 2 && (
<section>
<div className="eyebrow">Step 2 of 6</div>
<h1>Select your company size.</h1>
<p className="lede">This sets your FastTrack tier pricing. Pick one and we move you straight to outcomes.</p>
<div className="size-grid">
{TIERS.map((t) => (
<button key={t.id} className={"size-opt" + (tier?.id === t.id ? " sel" : "")} onClick={() => pickTier(t)}>
<span>
<span className="size-name">{t.name}</span>
<span className="size-sub">{t.sub}</span>
</span>
<span className="size-price">
{t.label}
<small>{t.mode === "fixed" ? "fixed price" : t.mode === "custom" ? "custom scope" : "starting price"}</small>
</span>
</button>
))}
</div>
<div className="step-nav">
<button className="btn btn-ghost" onClick={() => go(1)}>Back</button>
<span />
</div>
</section>
)}
{step === 3 && (
<section>
<div className="eyebrow">Step 3 of 6</div>
<h1>Select the outcomes you need.</h1>
<p className="lede">Pick one or more. Two outcomes are included in your tier. A third adds CAD $2,500, a fourth adds CAD $5,000, and five or more moves to custom scope.</p>
<div className="oc-grid">
{OUTCOMES.map((o) => {
const locked = o.launchOnly && tier && tier.id !== "t1"
const sel = outcomes.some((s) => s.id === o.id)
return (
<button key={o.id} className={"oc" + (sel ? " sel" : "")} disabled={!!locked} title={locked ? "Available only for organizations with 10 users or less" : o.name} onClick={() => toggleOutcome(o)}>
<span className="oc-check">{"\u2713"}</span>
<span className="oc-hook">{o.hook}</span>
<span className="oc-desc">{o.desc}</span>
{o.tag && <span className="oc-tag">{o.tag}</span>}
</button>
)
})}
</div>
{tier && tier.id !== "t1" && <div className="notice warn">Business Launch FastTrack is available only for organizations with 10 users or less.</div>}
{outcomes.length >= 5 && <div className="notice">Five or more outcomes require custom scope. Continue and Northaris will prepare custom pricing before anything is secured.</div>}
<div className="step-nav">
<button className="btn btn-ghost" onClick={() => go(2)}>Back</button>
<button className="btn btn-primary" disabled={outcomes.length === 0} onClick={() => go(4)}>Continue to start date</button>
</div>
</section>
)}
{step === 4 && (
<section>
<div className="eyebrow">Step 4 of 6</div>
<h1>When do you want to start?</h1>
<p className="lede">Your preferred start date shapes scheduling on our side. It is a preference, not a binding commitment.</p>
<div className="date-grid">
{DATES.map((d) => (
<button key={d} className={"date-opt" + (start === d ? " sel" : "")} onClick={() => pickDate(d)}>
{d}
</button>
))}
</div>
<div className="step-nav">
<button className="btn btn-ghost" onClick={() => go(3)}>Back</button>
<span />
</div>
</section>
)}
{step === 5 && (
<section>
<div className="eyebrow">Step 5 of 6</div>
<h1>Review exactly what you are buying.</h1>
<p className="lede">Read what Northaris gives you and what is not included. This review is required before payment or PO.</p>
<div className="scope-cols">
<div className="card">
<h2>What Northaris gives you</h2>
<ul>{GIVES.map((x) => <li key={x} className="li yes">{x}</li>)}</ul>
</div>
<div className="card">
<h2>What Northaris does not give you</h2>
<ul>{NOT_GIVES.map((x) => <li key={x} className="li no">{x}</li>)}</ul>
</div>
</div>
<h2 style={{ margin: "18px 0 10px" }}>Your selected outcome scope</h2>
{outcomes.map((o, i) => {
const open = openAcc[o.id] ?? (outcomes.length === 1 && i === 0)
return (
<div key={o.id} className={"acc" + (open ? " open" : "")}>
<button className="acc-head" onClick={() => setOpenAcc({ ...openAcc, [o.id]: !open })}>
{o.name}
<span>{"\u25BE"}</span>
</button>
{open && (
<div className="acc-body">
<h3>Included</h3>
<ul>{o.included.map((x: string) => <li key={x} className="li yes">{x}</li>)}</ul>
<h3>Deliverables</h3>
<ul>{o.deliverables.map((x: string) => <li key={x} className="li del">{x}</li>)}</ul>
<h3>Exclusions</h3>
<ul>{o.exclusions.map((x: string) => <li key={x} className="li no">{x}</li>)}</ul>
<div className="terms-box"><strong>Terms.</strong> {o.terms}</div>
</div>
)}
</div>
)
})}
<div className="packet-row">
<button className="btn btn-ghost" onClick={downloadPacket}>Download scope packet</button>
<button className="btn btn-ghost" onClick={emailPacket}>Email scope packet</button>
</div>
<label className={"ack" + (ack ? " on" : "")}>
<input type="checkbox" checked={ack} onChange={(e) => toggleAck(e.target.checked)} />
<p>I reviewed what Northaris gives me, what is not included, and the commercial terms.</p>
</label>
<div className="step-nav">
<button className="btn btn-ghost" onClick={() => go(4)}>Back</button>
<span />
</div>
</section>
)}
{step === 6 && (
<section>
<div className="eyebrow">Step 6 of 6</div>
<h1>Secure your FastTrack.</h1>
<p className="lede">Choose how your organization wants to commit. Both paths lock your scope and pricing as reviewed.</p>
<div className="pay-grid">
<div className="card pay-card">
<h2>Pay by card</h2>
<p>You will not be charged today. Your payment method secures kickoff. Payment is processed only after Northaris sends the agreed deliverables for review.</p>
<button className="btn btn-primary" onClick={secureCard}>Secure with card</button>
</div>
<div className="card pay-card">
<h2>Pay by purchase order</h2>
<p>Northaris will send a procurement-ready package with your selected outcomes, scope, exclusions, deliverables, pricing, estimated timeline, and vendor details.</p>
<button className="btn btn-ghost" onClick={securePO}>Request PO package</button>
</div>
</div>
{confirm && <div className="confirm">{confirm}</div>}
<div className="step-nav">
<button className="btn btn-ghost" onClick={() => go(5)}>Back</button>
<span />
</div>
</section>
)}
</main>
{/* -------- live summary -------- */}
<aside className="summary" aria-label="FastTrack summary">
<div className="sum-in">
<div className="sum-title">Your FastTrack</div>
<div className="sum-row"><span className="k">Prepared for</span><span className="v">{leadValid ? `${lead.name.trim()}, ${lead.company.trim()}` : "Not provided"}</span></div>
<div className="sum-row"><span className="k">Company size</span><span className="v">{tier ? tier.name : "Not selected"}</span></div>
<div className="sum-row"><span className="k">Tier pricing</span><span className="v">{pricing.tier}</span></div>
<div className="sum-outs">
<span className="k">Outcomes</span>
<ul>
{outcomes.length
? outcomes.map((o) => <li key={o.id}>{o.name}</li>)
: <li className="none">None selected yet</li>}
</ul>
</div>
<div className="sum-row"><span className="k">Multi-outcome</span><span className="v">{pricing.addon}</span></div>
<div className="sum-row"><span className="k">Preferred start</span><span className="v">{start || "Not selected"}</span></div>
<div className="sum-total"><span className="k">Estimated total</span><span className="v">{pricing.total}</span></div>
<div className="sum-note">
{pricing.custom
? "Custom scope. Northaris will confirm pricing in writing before anything is secured."
: "Fixed-scope assessment and planning engagement. Implementation is not included unless stated in writing."}
</div>
<div className="sum-credit">If you proceed with Northaris for remediation within 30 days of delivery, 50% of your FastTrack fee may be credited toward the approved implementation engagement.</div>
</div>
</aside>
<footer className="foot">
FastTrack is a fixed-scope Microsoft assessment and planning engagement. It is not a full implementation, migration, managed service, licensing resale agreement, helpdesk service, emergency support contract, or unlimited consulting engagement unless separately agreed in writing. The reports are yours to keep. The remediation credit applies only to implementation work directly related to the FastTrack findings and cannot be applied to licensing, hardware, third-party tools, managed services, or unrelated work. For purchase order clients, work begins once the PO, signed approval, or mutually accepted written authorization is received.
</footer>
</div>
</div>
)
}
addPropertyControls(NortharisFastTrack, {
webhookUrl: {
type: ControlType.String,
title: "Webhook URL",
defaultValue: "",
placeholder: "Power Automate HTTP trigger URL",
},
})