diff --git a/.gitignore b/.gitignore
index f773e52..848b0dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
.DS_Store
.vercel
node_modules
+.next
+out
+*.tsbuildinfo
+.env*.local
diff --git a/styles.css b/app/globals.css
similarity index 98%
rename from styles.css
rename to app/globals.css
index 14175c8..54df9c9 100644
--- a/styles.css
+++ b/app/globals.css
@@ -54,12 +54,12 @@ body::before {
z-index: 0;
}
/* theme variants — applied via body[data-theme] */
-body[data-theme="helm"] {
+[data-theme="helm"] {
--accent: #7ec5ff;
--accent-soft: rgba(126, 197, 255, 0.12);
--accent-glow: rgba(126, 197, 255, 0.35);
}
-body[data-theme="agency"] {
+[data-theme="agency"] {
--accent: #7892bd;
--accent-soft: rgba(120, 146, 189, 0.13);
--accent-glow: rgba(120, 146, 189, 0.38);
@@ -117,12 +117,12 @@ body.ready .spotlight { opacity: 1; }
}
/* Denim brand mark for 42nights — used on /, and as the parent-brand mark on pulse/helm navs */
.brand-mark.denim,
-body[data-theme="agency"] .brand-mark {
+[data-theme="agency"] .brand-mark {
background: linear-gradient(135deg, #88a3cd 0%, #5a7bb0 100%);
color: #f5f7fb;
box-shadow: 0 0 0 1px rgba(120, 146, 189, 0.35), 0 0 24px -2px rgba(120, 146, 189, 0.45);
}
-body[data-theme="helm"] .brand-mark:not(.denim) {
+[data-theme="helm"] .brand-mark:not(.denim) {
background: linear-gradient(135deg, #b6dcff 0%, #5a9adc 100%);
color: #07101c;
box-shadow: 0 0 0 1px rgba(126, 197, 255, 0.32), 0 0 24px -2px rgba(126, 197, 255, 0.45);
@@ -310,11 +310,6 @@ body[data-theme="helm"] .brand-mark:not(.denim) {
box-shadow: 0 0 0 1px rgba(205, 251, 83, 0.5), 0 6px 20px -4px var(--accent-glow);
transition: background 320ms var(--ease-out), box-shadow 320ms var(--ease-out);
}
-body[data-phase="pe"] .pt-indicator {
- background: var(--helm);
- box-shadow: 0 0 0 1px rgba(126, 197, 255, 0.5), 0 6px 20px -4px rgba(126, 197, 255, 0.35);
-}
-
/* hero-stage holds both phases stacked */
.hero-stage {
position: relative;
@@ -837,14 +832,10 @@ body[data-phase="pe"] .pt-indicator {
position: absolute; left: 16px; top: 50%;
width: 0;
height: 1px;
- background: linear-gradient(90deg, var(--accent), rgba(205,251,83,0.7));
+ background: linear-gradient(90deg, var(--accent), var(--accent-glow));
box-shadow: 0 0 10px var(--accent-glow);
transform: translateY(-50%);
}
-body[data-phase="pe"] .rm-progress {
- background: linear-gradient(90deg, var(--helm), rgba(126,197,255,0.7));
- box-shadow: 0 0 10px rgba(126,197,255,0.4);
-}
.rm-dot {
position: relative; z-index: 2;
width: 32px; height: 32px;
diff --git a/app/helm/page.tsx b/app/helm/page.tsx
new file mode 100644
index 0000000..a22e99a
--- /dev/null
+++ b/app/helm/page.tsx
@@ -0,0 +1,404 @@
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import GsapClient from '@/components/GsapClient';
+import Nav from '@/components/Nav';
+import Footer from '@/components/Footer';
+import Marquee from '@/components/Marquee';
+import Terminal from '@/components/Terminal';
+
+export const metadata: Metadata = {
+ title: 'Helm — the portfolio operating cockpit',
+ description:
+ 'KPI roll-ups across portcos, variance flags, board-pack agent. On infrastructure you control. Built on Recon, deployed by 42nights.',
+ alternates: { canonical: '/helm' },
+ openGraph: {
+ type: 'website',
+ url: '/helm',
+ title: 'Helm — portfolio cockpit on your infra',
+ description:
+ 'KPI roll-ups, variance flags, DD workspace, board-pack agent. Built on Recon, deployed by 42nights.',
+ siteName: '42nights',
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: 'Helm — portfolio cockpit on your infra',
+ description:
+ 'KPI roll-ups, variance flags, DD workspace, board-pack agent. Built on Recon, deployed by 42nights.',
+ },
+};
+
+export const viewport = { themeColor: '#7ec5ff' };
+
+const ArrowSm = () => (
+
+
+
+);
+
+export default function HelmPage() {
+ return (
+
+
+
+ {/* HERO */}
+
+
+
+
+ helm
+
+
+
+
+
+ Helm · Built on Recon · Deployed by 42nights
+
+
+
+
+ A portfolio
+
+
+ cockpit for
+
+
+ holdcos & {' '}
+ PE.
+
+
+
+ KPI roll-ups across portcos, variance flags, DD workspace, board-pack agent. On
+ infrastructure you control. Deployed by{' '}
+
+ 42nights
+ {' '}
+ in four weeks.
+
+
+ Operating partners · COOs · Holdcos · Family offices · LP teams
+
+
+
+
+
+
+
+ 0
+
+ % on your own infra
+
+
+
+
+ 0
+
+ PE skill packs
+
+
+
+
+ 0
+
+ week deploy
+
+
+
+
+
+
+
+ {/* CAPABILITIES */}
+
+
+ 01
+
+
+
+
+
+ What Helm does
+
+
+ From portco
+ to board pack.
+
+
+
+
+
+
+
+
+
+
+
+ portcos
+
+
+
+ holdco
+
+ KPI roll-ups
+ Portcos in. Dashboard out.
+
+
+
+
+
+ plan
+ actual
+
+
+
+
+ flag
+
+ Variance vs plan
+ Flagged. Drafted. Reviewed.
+
+
+
+
+
+ data room
+
+ CIM
+
+ model
+
+ notes
+
+
+ DD workspace
+
+ checklist
+
+ risk flags
+
+ DD workspace generator
+ Per-deal. Auto-tagged.
+
+
+
+
+
+
+
+
+
+
+
+ notes
+
+
+
+ BOARD
+ PACK
+ board ready
+
+ Board-pack agent
+ Notes in. Pack out.
+
+
+
+
+
+ {/* ROADMAP */}
+
+
+ 02
+
+
+
+
+
+ Roadmap
+
+
+ Five phases
+ per deployment.
+
+
+
+
+
+
+
+
+ 01
+
+
+ 02
+
+
+ 03
+
+
+ 04
+
+
+ 05
+
+
+
+
+
+
+
Phase 1 · Helm
+
Multi-entity foundation
+
+ Workspace-as-portfolio-company. HoldCo workspace aggregating read-only views.
+ Entities: Company, People, Contracts, KPIs, Reporting periods.
+
+
+
+
Phase 2 · Helm
+
Operating cadence
+
+ QuickBooks / Xero / NetSuite, Stripe, HRIS, Hub/Salesforce ingest. Auto-populated
+ monthly & quarterly templates with variance flags.
+
+
+
+
Phase 3 · Helm
+
Diligence & deal flow
+
+ Deal pipeline as a first-class object. Per-deal DD workspace generator.
+ Data-room ingest with auto-tagging and risk flag agents.
+
+
+
+
Phase 4 · Helm
+
Portfolio agents
+
+ Operating-partner agent per portco. Cross-portfolio benchmarking on margin, CAC
+ payback, NRR percentiles. Talent-network agent.
+
+
+
+
Phase 5 · Helm
+
LP & governance
+
+ LP reporting view (capital account, IRR/MOIC). Board-pack generator from
+ operating-partner output. SOC2-ready audit trail.
+
+
+
+
+
+
+
+
+ {/* PLATFORM REFERENCE */}
+
+
+
+
+
+
+ Built on Recon
+
+
Same gateway. Same vault. Same audit log.
+
+ Helm is a thin Next.js app over the Recon substrate. Each portco is a scoped
+ workspace; the holdco view is a read-only roll-up. Same security posture, same audit
+ trail.
+
+
+
See the Recon platform
+
+
+
+
+
+
+
+ {/* CTA */}
+
+
+ 03
+
+
+
+
+
+
+ Get Helm deployed
+
+
+ Four weeks to
+ a holdco cockpit.
+
+
+ 42nights deploys Helm inside your perimeter — one workspace per portco, holdco view
+ rolling up. Discovery to running in four weeks.
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/layout.tsx b/app/layout.tsx
new file mode 100644
index 0000000..a549382
--- /dev/null
+++ b/app/layout.tsx
@@ -0,0 +1,41 @@
+import type { Metadata } from 'next';
+import './globals.css';
+
+export const metadata: Metadata = {
+ metadataBase: new URL('https://42nights.dev'),
+ title: { default: '42nights', template: '%s · 42nights' },
+ description: '42nights deploys AI operating layers inside your perimeter.',
+ icons: { icon: '/favicon.svg' },
+};
+
+export const viewport = {
+ themeColor: '#7892bd',
+};
+
+const fontHref =
+ 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Instrument+Serif:ital@0;1&display=swap';
+
+export default function RootLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..66e45b0
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,481 @@
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import GsapClient from '@/components/GsapClient';
+import Nav from '@/components/Nav';
+import Footer from '@/components/Footer';
+import Marquee from '@/components/Marquee';
+import Terminal from '@/components/Terminal';
+
+export const metadata: Metadata = {
+ title: '42nights — engineering studio · SF',
+ description:
+ 'An in-person engineering studio. We embed for four weeks, ship Recon (or Pulse, or Helm) on your infrastructure, and hand the keys to your team.',
+ alternates: { canonical: '/' },
+ openGraph: {
+ type: 'website',
+ url: '/',
+ title: '42nights — engineering studio · SF',
+ description:
+ 'We embed for four weeks and ship the operating layer that runs your business — inside your perimeter.',
+ siteName: '42nights',
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: '42nights — engineering studio · SF',
+ description:
+ 'We embed for four weeks and ship the operating layer that runs your business — inside your perimeter.',
+ },
+};
+
+export const viewport = { themeColor: '#7892bd' };
+
+const ArrowSm = () => (
+
+
+
+);
+
+export default function StudioPage() {
+ return (
+
+
+
+ {/* HERO */}
+
+
+
+
+ 42n
+
+
+
+
+
+ Engineering studio · SF + on-site · est. ’24
+
+
+
+
+ We embed
+
+
+ inside your {' '}
+ team
+
+
+ and ship {' '}
+ the
+
+
+ operating {' '}
+ layer
+
+
+ that runs {' '}
+ your business.
+
+
+
+
+ 42nights is a four-week, on-site engineering studio. We deploy{' '}
+
+ Recon
+ {' '}
+ — and its variants{' '}
+
+ Pulse
+ {' '}
+ for revenue teams and{' '}
+
+ Helm
+ {' '}
+ for portfolio teams — inside your perimeter, then hand the keys to your operators.
+
+
+
+
+
+
+
+ 0
+
+ week engagement
+
+
+
+
+ 0
+
+ % on your infra
+
+
+
+
+ 0
+
+ products deployed
+
+
+
+
+ 0
+
+ day post-deploy support
+
+
+
+
+
+
+
+ {/* APPROACH */}
+
+
+ 01
+
+
+
+
+
+ Approach
+
+
+ Four weeks. On site.
+
+ Then it's yours.
+
+
+ We don't write a deck and disappear. We sit at your desk, watch your operators
+ work, and ship the system that fits — then we leave.
+
+
+
+
+
+ W1
+
+
Discovery on the ground.
+
+ One week shadowing your team. We map workflows, identify which pieces of Recon
+ (or Pulse / Helm) fit, and write the deployment plan with you.
+
+
+ scope & plan
+
+
+ W2
+
+
Deploy the substrate.
+
+ Recon goes inside your VPC, on-prem, or air-gapped — whichever your security
+ review demands. Identity, vault, gateway, audit log, all live by end of week.
+
+
+ platform live
+
+
+ W3
+
+
Ship the surface.
+
+ Pulse for revenue, Helm for portfolio, or a custom variant. Your data is wired,
+ your skills are tuned, your operators are running real workflows by Friday.
+
+
+ surface running
+
+
+ W4
+
+
Hand off.
+
+ Train your team, document the deploy, transfer source escrow, set up the 90-day
+ support cadence. We leave on Friday. The platform stays.
+
+
+ team owns it
+
+
+
+
+
+ {/* PRODUCTS WE DEPLOY */}
+
+
+ 02
+
+
+
+
+
+ What we ship
+
+
+ One platform.
+
+ Two specialized cockpits.
+
+
+ We built Recon ourselves. Pulse and Helm wrap it for revenue and portfolio teams
+ respectively. We deploy whichever fits — and we'll fork a custom variant if
+ neither does.
+
+
+
+
+
+
+ platform
+
+ →
+
+
+
Recon
+
+ The substrate. Self-hosted gateway, identity vault, agent runtime, audit log.
+
+
+ Common to both
+ Lives inside your perimeter
+ Forkable
+
+
+
+
+ GTM
+
+ →
+
+
+
Pulse
+
+ Revenue cockpit. Identity-resolved CRM, scheduled agents, attribution, forecast.
+
+
+ RevOps · Sales · Growth
+ Inbox-first
+ Agent-driven outbound
+
+
+
+
+ PE
+
+ →
+
+
+
Helm
+
+ Portfolio cockpit. KPI roll-ups, variance flags, DD workspace, board-pack agent.
+
+
+ Operating partners · Holdcos
+ Per-portco scoping
+ SOC2-ready trail
+
+
+
+
+
+
+ {/* WHY US */}
+
+
+ 03
+
+
+
+
+
+ Why 42nights
+
+
+ Operator-led.
+
+ Source-available. On-site.
+
+
+
+
+
+ ①
+
+
We built the thing we deploy.
+
+ Recon is ours. When something doesn't fit your stack, we change the
+ substrate, not the contract.
+
+
+
+
+ ②
+
+
Sovereignty is the default, not an upgrade.
+
+ Single-tenant by definition. Your VPC, your keys, your audit log. We pass your
+ security review on day one.
+
+
+
+
+ ③
+
+
Hands-on, not advisory.
+
+ We deploy and operate alongside your team — not in a slide deck from a thousand
+ miles away. The handoff is real.
+
+
+
+
+
+
+
+ {/* ENGAGEMENT */}
+
+
+ 04
+
+
+
+
+
+ Engagement
+
+
+ Fixed-fee. In person.
+
+ Source escrow.
+
+
+
+
+
+
Format
+
+ Four-week minimum, fully on-site at your office. Two engineers from us, embedded
+ with your operators.
+
+
+
+
Fee
+
+ Fixed-fee per engagement, scoped in week one. No hourly billing, no out-of-scope
+ creep.
+
+
+
+
IP
+
+ Source escrow at deploy. Custom skill packs are yours. Recon core stays under our
+ license, available to you in perpetuity for that deployment.
+
+
+
+
Support
+
+ Ninety days post-deploy on retainer — priority Slack, weekly check-ins, hot-fixes
+ within 24 hours.
+
+
+
+
Capacity
+ Three active engagements at a time, max. We're a studio, not a body shop.
+
+
+
+
+
+ {/* CONTACT */}
+
+
+
+
+
+ );
+}
diff --git a/app/pulse/page.tsx b/app/pulse/page.tsx
new file mode 100644
index 0000000..df5c47e
--- /dev/null
+++ b/app/pulse/page.tsx
@@ -0,0 +1,399 @@
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import GsapClient from '@/components/GsapClient';
+import Nav from '@/components/Nav';
+import Footer from '@/components/Footer';
+import Marquee from '@/components/Marquee';
+import Terminal from '@/components/Terminal';
+
+export const metadata: Metadata = {
+ title: 'Pulse — the GTM operating cockpit',
+ description:
+ 'Identity-resolved CRM, scheduled agents, attribution and forecast — on infrastructure you control. Built on Recon, deployed by 42nights.',
+ alternates: { canonical: '/pulse' },
+ openGraph: {
+ type: 'website',
+ url: '/pulse',
+ title: 'Pulse — GTM cockpit on your infra',
+ description:
+ 'The operating layer between signal and revenue. Built on Recon, deployed by 42nights in four weeks.',
+ siteName: '42nights',
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: 'Pulse — GTM cockpit on your infra',
+ description:
+ 'The operating layer between signal and revenue. Built on Recon, deployed by 42nights in four weeks.',
+ },
+};
+
+export const viewport = { themeColor: '#cdfb53' };
+
+const ArrowSm = () => (
+
+
+
+);
+
+export default function PulsePage() {
+ return (
+
+
+
+ {/* HERO */}
+
+
+
+
+ pulse
+
+
+
+
+
+ Pulse · Built on Recon · Deployed by 42nights
+
+
+
+
+ The operating {' '}
+ layer
+
+
+ between signal
+
+
+ and revenue.
+
+
+
+ Identity-resolved CRM, inbox-first sync, scheduled agents, forecast that ties to source.
+ On infrastructure you control. Deployed by{' '}
+
+ 42nights
+ {' '}
+ in four weeks.
+
+
+ RevOps · Sales leadership · Growth ops · CROs
+
+
+
+
+
+
+
+ 0
+
+ % on your own infra
+
+
+
+
+ 0
+
+ GTM skill packs
+
+
+
+
+ 0
+
+ week deploy
+
+
+
+
+
+
+
+ {/* CAPABILITIES */}
+
+
+ 01
+
+
+
+
+
+ What Pulse does
+
+
+ From inbox
+ to closed-won.
+
+
+
+
+
+
+
+ maya@northstar.io
+
+ m.adams@northstar.io
+
+ li/maya-adams
+
+
+
+
+ M. Adams
+
+ Identity-resolved CRM
+ Three channels. One row.
+
+
+
+
+
+
+ trigger
+
+
+ RUN
+
+
+ DIFF
+ review
+
+ Per-account next-best-action
+ Trigger → run → diff back.
+
+
+
+
+
+ posthog
+
+ apollo
+
+ clearbit
+
+ webhook
+
+ linkedin
+
+
+ signals
+
+ One surface for intent
+ Many sources. One stream.
+
+
+
+
+
+
+
+
+
+
+
+ forecast
+
+ Forecast that ties to source
+ Stage · owner · segment.
+
+
+
+
+
+ {/* ROADMAP */}
+
+
+ 02
+
+
+
+
+
+ Roadmap
+
+
+ Five phases
+ per deployment.
+
+
+
+
+
+
+
+
+ 01
+
+
+ 02
+
+
+ 03
+
+
+ 04
+
+
+ 05
+
+
+
+
+
+
+
Phase 1 · Pulse
+
Pipeline foundations
+
+ Canonical People + Company model. Inbox-first sync. Calendar → meeting → deal
+ attribution. Lifecycle stage, owner, last touch.
+
+
+
+
Phase 2 · Pulse
+
Signal capture
+
+ Apollo / Clay enrichment. PostHog event timeline. LinkedIn + outbound capture.
+ Intent adapters behind one signals surface.
+
+
+
+
Phase 3 · Pulse
+
Agentic execution
+
+ Lead triage · ICP scoring · outbound draft · meeting prep · pipeline review ·
+ churn flag. Per-account next-best-action on a schedule.
+
+
+
+
Phase 4 · Pulse
+
Revenue analytics
+
+ Weighted forecast by stage / owner / segment. Source → meeting → opp
+ attribution. Cohort retention via PostHog or warehouse adapter.
+
+
+
+
Phase 5 · Pulse
+
Multiplayer
+
+ Team workspaces with row-level RBAC. Shared skill libraries with review/approval.
+ Slack / Linear two-way sync for handoffs.
+
+
+
+
+
+
+
+
+ {/* PLATFORM REFERENCE */}
+
+
+
+
+
+
+ Built on Recon
+
+
Same gateway. Same vault. Same audit log.
+
+ Pulse is a thin Next.js app over the Recon substrate. Your data plane, security
+ model, and operator skills are the platform's — Pulse just wears the GTM chrome
+ on top.
+
+
+
See the Recon platform
+
+
+
+
+
+
+
+ {/* CTA */}
+
+
+ 03
+
+
+
+
+
+
+ Get Pulse deployed
+
+
+ Four weeks to
+ your own pipeline OS.
+
+
+ 42nights deploys Pulse inside your perimeter. Discovery on Monday, your team running
+ it on the Friday of week four.
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/recon/page.tsx b/app/recon/page.tsx
new file mode 100644
index 0000000..6ed0f85
--- /dev/null
+++ b/app/recon/page.tsx
@@ -0,0 +1,784 @@
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import GsapClient from '@/components/GsapClient';
+import Nav from '@/components/Nav';
+import Footer from '@/components/Footer';
+import Marquee from '@/components/Marquee';
+import Terminal from '@/components/Terminal';
+
+export const metadata: Metadata = {
+ title: 'Recon — operating substrate for ops-heavy teams',
+ description:
+ 'The self-hosted, single-tenant, audit-ready platform that Pulse and Helm are built on. Deployed by 42nights.',
+ alternates: { canonical: '/recon' },
+ openGraph: {
+ type: 'website',
+ url: '/recon',
+ title: 'Recon — operating substrate for ops-heavy teams',
+ description:
+ 'Self-hosted gateway, identity vault, agent runtime. The platform Pulse and Helm are built on.',
+ siteName: '42nights',
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title: 'Recon — operating substrate for ops-heavy teams',
+ description:
+ 'Self-hosted gateway, identity vault, agent runtime. The platform Pulse and Helm are built on.',
+ },
+};
+
+export const viewport = { themeColor: '#cdfb53' };
+
+const ArrowSm = () => (
+
+
+
+);
+
+const ArrowSmInRow = () => (
+
+
+
+);
+
+// helper: glass-box positioning vars
+const pos = (x: number, y: number, w: number, h: number): React.CSSProperties =>
+ ({
+ ['--x' as string]: x,
+ ['--y' as string]: y,
+ ['--w' as string]: w,
+ ['--h' as string]: h,
+ }) as React.CSSProperties;
+
+export default function ReconPage() {
+ return (
+
+
+
+ {/* HERO */}
+
+
+
+
+
+
+
+
+
+
+ recon
+
+
+
+
+
+ Self-hosted · Single-tenant · Audit-ready
+
+
+
+
+ The operating {' '}
+ substrate
+
+
+ for ops-heavy {' '}
+ teams.
+
+
+
+ Recon is the self-hosted gateway, workspace, and skill runtime that{' '}
+
+ Pulse
+ {' '}
+ and{' '}
+
+ Helm
+ {' '}
+ are built on. Deployed by{' '}
+
+ 42nights
+ {' '}
+ inside your perimeter.
+
+
+ RevOps · Sales leadership · Operating partners · COOs · Family offices
+
+
+
+
+
+
+
+ 0
+
+ % on your own infra
+
+
+
+
+ 0
+
+ product surfaces
+
+
+
+
+ 0
+
+ vendor lock-in
+
+
+
+
+
+
+
+ {/* WHY (perimeter diagram) */}
+
+
+ 01
+
+
+
+
+
+ Why operators choose Recon
+
+
+ Your data. Your perimeter. Your agents.
+
+
+
+
+
+
+
+ Recon stays inside your perimeter; connectors call out, never in
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ YOUR PERIMETER
+
+
+ VPC · laptop · air-gapped
+
+
+
+
Pulse
+
GTM workspace
+
+
+
Helm
+
Portfolio cockpit
+
+
+
+
Gateway
+
audit log · vault · skill runtime
+
+
+
+
+
+ Salesforce · HubSpot
+
+
+ Stripe · QuickBooks · Xero
+
+
+ Gmail · Outlook · Calendar
+
+
+ Apollo · Clay · PostHog
+
+
+ MCP servers · LLM APIs
+
+
+
+
+ ✕
+
+ no inbound
+
+
+ → outbound only
+
+
+ audit log signed
+
+
+ your keys, your network
+
+
+
+
+
+
+
+ {/* PRODUCTS */}
+
+
+ 02
+
+
+
+
+
+ Two products · one substrate
+
+
+ Pulse for revenue.
+
+ Helm for portfolios.
+
+
+ Both wrap the same self-hosted core — same gateway, same identity vault, same skill
+ substrate. Different operators, very different jobs to be done.
+
+
+
+
+
+
+
+
+
Pulse
+
GTM operating system
+
+
+
+ The operating layer between raw signal and revenue execution.
+
+
+ Identity-resolved CRM, inbox-first sync, Apollo / Clay-style enrichment, scheduled
+ outbound, attribution and forecast — all on infrastructure you control.
+
+
+
RevOps · Sales · Growth
+
+
+
+
+
+
+
+
+
+
+
+
Helm
+
Portfolio operating cockpit
+
+
+
+ A holdco cockpit — every portco a workspace, rolling up to one view.
+
+
+ KPI ingest from QuickBooks, Stripe, HRIS, HubSpot. Variance flags with agent-drafted
+ commentary. Per-deal DD workspace generator. Cross-portfolio benchmarking on margin,
+ CAC payback, NRR.
+
+
+
+ Operating partners · Holdcos · Family offices
+
+
+
+
+
+
+
+
+
+
+
+ {/* HOW (architecture stack) */}
+
+
+ 03
+
+
+
+
+
+ How it deploys
+
+
+ Three layers. One process tree.
+
+
+
+
+
+
Shells
+
+ apps/web
+ Pulse
+ Helm
+
+
+
+
+
Gateway
+
+ profile-managed daemon
+ audit signer
+ credential vault
+
+
+
+
+
Extensions
+
+ AI gateway
+ Identity
+ Composio
+ Apollo
+ PostHog
+
+
+
+
+ macOS
+ Linux
+ Docker
+ VPC
+ Air-gapped
+
+
+
+
+
+ {/* CAPABILITIES */}
+
+
+ 04
+
+
+
+
+
+ Capabilities
+
+
+ What it does, out of the box.
+
+
+
+
+
+
+
+ maya@northstar.io
+
+ m.adams@northstar.io
+
+ li/maya-adams
+
+
+
+
+ M. Adams
+
+ Identity-resolved CRM
+ Three channels. One row.
+
+
+
+
+
+
+ trigger
+
+
+ RUN
+
+
+ DIFF
+ review
+
+ Skills act on schedule
+ Trigger → run → diff back.
+
+
+
+
+
+ posthog
+
+ apollo
+
+ clearbit
+
+ webhook
+
+ linkedin
+
+
+ signals
+
+ One surface for intent
+ Many sources. One stream.
+
+
+
+
+
+
+
+
+
+ portcos
+
+
+
+ holdco
+
+ Holdco KPI roll-ups
+ Portcos in. Dashboard out.
+
+
+
+
+
+ {/* ROADMAP */}
+
+
+ 05
+
+
+
+
+
+ Roadmap
+
+
+ Five phases. Two products. One substrate.
+
+
+
+
+
+ Pulse — GTM
+
+
+ Helm — PE
+
+
+
+
+
+
+
+
+
+ 01
+
+
+ 02
+
+
+ 03
+
+
+ 04
+
+
+ 05
+
+
+
+
+
+
+
Phase 1 · Pulse
+
Pipeline foundations
+
+ Canonical People + Company model. Inbox-first sync. Calendar → meeting → deal
+ attribution. Lifecycle stage, owner, last touch.
+
+
+
+
Phase 2 · Pulse
+
Signal capture
+
+ Apollo / Clay enrichment. PostHog event timeline. LinkedIn + outbound capture.
+ Intent adapters behind one signals surface.
+
+
+
+
Phase 3 · Pulse
+
Agentic execution
+
+ Lead triage · ICP scoring · outbound draft · meeting prep · pipeline review ·
+ churn flag. Per-account next-best-action on a schedule.
+
+
+
+
Phase 4 · Pulse
+
Revenue analytics
+
+ Weighted forecast by stage / owner / segment. Source → meeting → opp
+ attribution. Cohort retention via PostHog or warehouse adapter.
+
+
+
+
Phase 5 · Pulse
+
Multiplayer
+
+ Team workspaces with row-level RBAC. Shared skill libraries with review/approval.
+ Slack / Linear two-way sync for handoffs.
+
+
+
+
+
+
+
Phase 1 · Helm
+
Multi-entity foundation
+
+ Workspace-as-portfolio-company. HoldCo workspace aggregating read-only views.
+ Entities: Company, People, Contracts, KPIs, Reporting periods.
+
+
+
+
Phase 2 · Helm
+
Operating cadence
+
+ QuickBooks / Xero / NetSuite, Stripe, HRIS, Hub/Salesforce ingest. Auto-populated
+ monthly & quarterly templates with variance flags.
+
+
+
+
Phase 3 · Helm
+
Diligence & deal flow
+
+ Deal pipeline as a first-class object. Per-deal DD workspace generator.
+ Data-room ingest with auto-tagging and risk flag agents.
+
+
+
+
Phase 4 · Helm
+
Portfolio agents
+
+ Operating-partner agent per portco. Cross-portfolio benchmarking on margin, CAC
+ payback, NRR percentiles. Talent-network agent.
+
+
+
+
Phase 5 · Helm
+
LP & governance
+
+ LP reporting view (capital account, IRR/MOIC). Board-pack generator from
+ operating-partner output. SOC2-ready audit trail.
+
+
+
+
+
+
+
+
+ {/* TRUST */}
+
+
+ 06
+
+
+
+
+
+ Trust
+
+
+ Built to pass
+ your security review.
+
+
+
+ Single-tenant by definition. Bring your own keys for LLM, CRM, storage, and identity.
+ SOC2-ready audit log per workspace, per skill, per credential — pipe it straight to your
+ SIEM. Zero telemetry, verifiable in source provided under your contract.
+ Workspace-scoped RBAC isolates your sales workspace from your portfolio data, by
+ default.
+
+
+ SOC2-ready audit trail
+ Bring your own keys
+ On-prem / VPC / Air-gapped
+ Single-tenant by design
+ Zero telemetry
+ Workspace-scoped RBAC
+
+
+
+
+ {/* CTA */}
+
+
+ 07
+
+
+
+
+
+
+ Get started
+
+
+ Run it where your
+ data already lives.
+
+
+ We deploy Recon inside your perimeter and stay close through onboarding,
+ integration, and skill-pack tuning. Talk to us about scope, security, and pricing.
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/Footer.tsx b/components/Footer.tsx
new file mode 100644
index 0000000..98ed2e9
--- /dev/null
+++ b/components/Footer.tsx
@@ -0,0 +1,51 @@
+import Link from 'next/link';
+
+interface Props {
+ variant?: 'studio' | 'product';
+ productName?: 'Recon' | 'Pulse' | 'Helm';
+}
+
+export default function Footer({ variant = 'product', productName = 'Recon' }: Props) {
+ const isStudio = variant === 'studio';
+
+ return (
+
+
+
+ {isStudio ? (
+ <>
+
+
+
+
+
+
+
+
42nights · engineering studio · SF
+ >
+ ) : (
+
+ {productName} · built & deployed by 42nights
+
+ )}
+
+
+
Studio
+
Recon
+
Pulse
+
Helm
+
Contact
+
+
+
+ );
+}
diff --git a/components/GsapClient.tsx b/components/GsapClient.tsx
new file mode 100644
index 0000000..27dce91
--- /dev/null
+++ b/components/GsapClient.tsx
@@ -0,0 +1,427 @@
+'use client';
+
+import { useEffect } from 'react';
+import { usePathname } from 'next/navigation';
+import { gsap } from 'gsap';
+import { ScrollTrigger } from 'gsap/ScrollTrigger';
+
+const EASE_OUT_HARD = 'cubic-bezier(0.23, 1, 0.32, 1)';
+
+export default function GsapClient() {
+ const pathname = usePathname();
+
+ useEffect(() => {
+ if (typeof window === 'undefined') return;
+ const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+
+ gsap.registerPlugin(ScrollTrigger);
+ document.body.classList.add('ready');
+
+ /* ─── spotlight follow ─── */
+ let spotlightCleanup: (() => void) | undefined;
+ {
+ const el = document.querySelector('.spotlight') as HTMLElement | null;
+ if (el && !reduceMotion) {
+ gsap.set(el, { xPercent: -50, yPercent: -50, top: 0, left: 0 });
+ const xTo = gsap.quickTo(el, 'x', { duration: 0.7, ease: 'power3.out' });
+ const yTo = gsap.quickTo(el, 'y', { duration: 0.7, ease: 'power3.out' });
+ xTo(window.innerWidth / 2);
+ yTo(window.innerHeight / 3);
+ const onMove = (e: PointerEvent) => {
+ xTo(e.clientX);
+ yTo(e.clientY);
+ };
+ window.addEventListener('pointermove', onMove, { passive: true });
+ spotlightCleanup = () => window.removeEventListener('pointermove', onMove);
+ }
+ }
+
+ /* ─── parallax ─── */
+ if (!reduceMotion) {
+ document.querySelectorAll('[data-parallax]').forEach((el) => {
+ const speed = parseFloat(el.dataset.parallax || '');
+ if (!isFinite(speed) || speed <= 0) return;
+ const trigger = (el.closest('.section, .hero') as HTMLElement) || el.parentElement;
+ gsap.to(el, {
+ yPercent: -40 * speed,
+ ease: 'none',
+ scrollTrigger: {
+ trigger,
+ start: 'top bottom',
+ end: 'bottom top',
+ scrub: 0.4,
+ },
+ });
+ });
+ }
+
+ /* ─── hero title reveal ─── */
+ {
+ const scope =
+ document.querySelector('.hero-phase.is-active') ||
+ document.querySelector('.hero');
+ if (scope) {
+ const words = scope.querySelectorAll('.hero-title .word');
+ if (words.length) {
+ gsap.from(words, {
+ yPercent: 110,
+ filter: 'blur(8px)',
+ opacity: 0,
+ duration: 0.95,
+ ease: 'power4.out',
+ stagger: 0.05,
+ delay: 0.1,
+ });
+ }
+ }
+ }
+
+ /* ─── data-reveal scroll fades ─── */
+ document.querySelectorAll('[data-reveal]').forEach((el) => {
+ gsap.fromTo(
+ el,
+ { opacity: 0, y: 22, filter: 'blur(6px)' },
+ {
+ opacity: 1,
+ y: 0,
+ filter: 'blur(0px)',
+ duration: 0.8,
+ ease: 'power3.out',
+ scrollTrigger: { trigger: el, start: 'top 88%', toggleActions: 'play none none none' },
+ }
+ );
+ });
+
+ /* ─── data-stagger groups ─── */
+ document.querySelectorAll('[data-stagger]').forEach((group) => {
+ const items = Array.from(group.children) as HTMLElement[];
+ if (!items.length) return;
+ gsap.set(items, { opacity: 0, y: 14 });
+ gsap.to(items, {
+ opacity: 1,
+ y: 0,
+ duration: 0.55,
+ ease: EASE_OUT_HARD,
+ stagger: 0.05,
+ scrollTrigger: { trigger: group, start: 'top 88%', toggleActions: 'play none none none' },
+ });
+ });
+
+ /* ─── number counters ─── */
+ document.querySelectorAll('[data-counter]').forEach((node) => {
+ const target = parseFloat(node.dataset.counter || '0');
+ const proxy = { v: 0 };
+ ScrollTrigger.create({
+ trigger: node,
+ start: 'top 90%',
+ once: true,
+ onEnter: () => {
+ gsap.to(proxy, {
+ v: target,
+ duration: 1.4,
+ ease: 'power3.out',
+ onUpdate: () => {
+ node.textContent = Math.round(proxy.v).toLocaleString();
+ },
+ });
+ },
+ });
+ });
+
+ /* ─── marquee ─── */
+ let marqueeTween: gsap.core.Tween | undefined;
+ {
+ const track = document.querySelector('.marquee-track') as HTMLElement | null;
+ if (track) {
+ track.innerHTML += track.innerHTML;
+ const distance = track.scrollWidth / 2;
+ marqueeTween = gsap.to(track, {
+ x: -distance,
+ duration: 40,
+ ease: 'none',
+ repeat: -1,
+ });
+ const parent = track.parentElement;
+ if (parent) {
+ parent.addEventListener('mouseenter', () => marqueeTween?.timeScale(0.1));
+ parent.addEventListener('mouseleave', () => marqueeTween?.timeScale(1));
+ }
+ }
+ }
+
+ /* ─── nav: compact + sliding indicator + scroll-spy ─── */
+ {
+ const nav = document.querySelector('.nav') as HTMLElement | null;
+ const links = document.querySelectorAll('.nav-link[data-nav]');
+ const indicator = document.querySelector('.nav-indicator') as HTMLElement | null;
+ const linksList = document.querySelector('.nav-links') as HTMLElement | null;
+
+ if (nav) {
+ ScrollTrigger.create({
+ start: 'top -40',
+ onUpdate: (self) => nav.classList.toggle('compact', self.scroll() > 40),
+ });
+ }
+
+ const moveTo = (el: HTMLElement) => {
+ if (!indicator || !linksList) return;
+ const r = el.getBoundingClientRect();
+ const parent = linksList.getBoundingClientRect();
+ gsap.to(indicator, {
+ x: r.left - parent.left,
+ y: r.top - parent.top,
+ width: r.width,
+ height: r.height,
+ opacity: 1,
+ duration: 0.4,
+ ease: 'power3.out',
+ });
+ };
+ links.forEach((l) => l.addEventListener('pointerenter', () => moveTo(l)));
+ linksList?.addEventListener('pointerleave', () => {
+ if (indicator) gsap.to(indicator, { opacity: 0, duration: 0.3 });
+ });
+
+ // scroll spy on in-page sections (only on pages with hash links)
+ ['#why', '#products', '#how', '#roadmap', '#approach', '#engagement', '#contact'].forEach(
+ (id) => {
+ const sec = document.querySelector(id);
+ if (!sec) return;
+ ScrollTrigger.create({
+ trigger: sec,
+ start: 'top center',
+ end: 'bottom center',
+ onToggle: (self) => {
+ if (!self.isActive) return;
+ links.forEach((l) =>
+ l.classList.toggle('is-active', l.getAttribute('href') === id)
+ );
+ const active = document.querySelector('.nav-link.is-active') as HTMLElement | null;
+ if (active) moveTo(active);
+ },
+ });
+ }
+ );
+ }
+
+ /* ─── magnetic buttons ─── */
+ if (!reduceMotion) {
+ document.querySelectorAll('[data-magnetic]').forEach((el) => {
+ const xTo = gsap.quickTo(el, 'x', { duration: 0.5, ease: 'elastic.out(1, 0.4)' });
+ const yTo = gsap.quickTo(el, 'y', { duration: 0.5, ease: 'elastic.out(1, 0.4)' });
+ const onMove = (e: PointerEvent) => {
+ const r = el.getBoundingClientRect();
+ const dx = e.clientX - (r.left + r.width / 2);
+ const dy = e.clientY - (r.top + r.height / 2);
+ xTo(dx * 0.22);
+ yTo(dy * 0.32);
+ };
+ const onLeave = () => {
+ xTo(0);
+ yTo(0);
+ };
+ el.addEventListener('pointermove', onMove);
+ el.addEventListener('pointerleave', onLeave);
+ });
+ }
+
+ /* ─── product card hover-glow follow ─── */
+ document.querySelectorAll('.product-card').forEach((card) => {
+ card.addEventListener('pointermove', (e) => {
+ const r = card.getBoundingClientRect();
+ card.style.setProperty('--gx', ((e.clientX - r.left) / r.width) * 100 + '%');
+ card.style.setProperty('--gy', ((e.clientY - r.top) / r.height) * 100 + '%');
+ });
+ });
+
+ /* ─── roadmap tabs + horizontal phase timeline ─── */
+ {
+ const btns = document.querySelectorAll('.rt-btn');
+ const indicator = document.querySelector('.rt-indicator') as HTMLElement | null;
+ const panelGroups = document.querySelectorAll('[data-roadmap-panel]');
+ const dots = document.querySelectorAll('.rm-dot');
+ const progress = document.querySelector('.rm-progress') as HTMLElement | null;
+ const timeline = document.querySelector('.rm-timeline') as HTMLElement | null;
+
+ const placeRtIndicator = (btn: HTMLElement) => {
+ if (!indicator || !btn.parentElement) return;
+ const tabsRect = btn.parentElement.getBoundingClientRect();
+ const r = btn.getBoundingClientRect();
+ gsap.to(indicator, {
+ x: r.left - tabsRect.left,
+ y: r.top - tabsRect.top,
+ width: r.width,
+ height: r.height,
+ duration: 0.42,
+ ease: 'power3.out',
+ });
+ };
+
+ const initial = document.querySelector('.rt-btn.active') as HTMLElement | null;
+ if (initial) requestAnimationFrame(() => placeRtIndicator(initial));
+
+ const updateProgress = () => {
+ if (!progress || !dots.length || !timeline) return;
+ const track = timeline.querySelector('.rm-track') as HTMLElement | null;
+ if (!track) return;
+ const activeIdx = parseInt(timeline.dataset.activePhase || '0', 10);
+ const pct = dots.length > 1 ? activeIdx / (dots.length - 1) : 0;
+ const width = pct * (track.clientWidth - 32);
+ gsap.to(progress, { width, duration: 0.55, ease: 'power3.out' });
+ };
+
+ const activatePhase = (idx: number) => {
+ if (!timeline) return;
+ timeline.dataset.activePhase = String(idx);
+ dots.forEach((d, i) => d.classList.toggle('active', i === idx));
+ const visibleGroup = document.querySelector('.rm-panels.active');
+ if (visibleGroup) {
+ const panels = visibleGroup.querySelectorAll('.rm-panel');
+ panels.forEach((p, i) => p.classList.toggle('active', i === idx));
+ const target = panels[idx];
+ if (target) {
+ gsap.fromTo(
+ target,
+ { opacity: 0, y: 10, filter: 'blur(4px)' },
+ { opacity: 1, y: 0, filter: 'blur(0px)', duration: 0.45, ease: 'power3.out' }
+ );
+ }
+ }
+ updateProgress();
+ };
+
+ dots.forEach((dot, idx) => {
+ dot.addEventListener('click', () => activatePhase(idx));
+ });
+
+ btns.forEach((btn) => {
+ btn.addEventListener('click', () => {
+ btns.forEach((b) => {
+ b.classList.remove('active');
+ b.setAttribute('aria-selected', 'false');
+ });
+ btn.classList.add('active');
+ btn.setAttribute('aria-selected', 'true');
+ placeRtIndicator(btn);
+
+ const target = btn.dataset.roadmap;
+ panelGroups.forEach((g) => {
+ const isTarget = g.dataset.roadmapPanel === target;
+ g.classList.toggle('active', isTarget);
+ if (isTarget) {
+ const visiblePanel = g.querySelector('.rm-panel.active') as HTMLElement | null;
+ if (visiblePanel) {
+ gsap.fromTo(
+ visiblePanel,
+ { opacity: 0, y: 10, filter: 'blur(4px)' },
+ { opacity: 1, y: 0, filter: 'blur(0px)', duration: 0.45, ease: 'power3.out' }
+ );
+ }
+ }
+ });
+ });
+ });
+
+ const onResize = () => {
+ const active = document.querySelector('.rt-btn.active') as HTMLElement | null;
+ if (active) placeRtIndicator(active);
+ updateProgress();
+ };
+ window.addEventListener('resize', onResize);
+ requestAnimationFrame(updateProgress);
+ }
+
+ /* ─── terminal typewriter ─── */
+ {
+ const body = document.getElementById('terminal-body');
+ if (body) {
+ const cmds = body.querySelectorAll('.cmd');
+ const outs = body.querySelectorAll('.t-line.out');
+ cmds.forEach((c) => {
+ c.dataset.text = c.dataset.typed;
+ c.textContent = '';
+ });
+ outs.forEach((o) => gsap.set(o, { opacity: 0, height: 0, overflow: 'hidden' }));
+ const lines = Array.from(body.children) as HTMLElement[];
+
+ const playLine = (line: HTMLElement) =>
+ new Promise((resolve) => {
+ if (line.classList.contains('out')) {
+ gsap.to(line, { opacity: 1, height: 'auto', duration: 0.25, onComplete: () => resolve() });
+ } else {
+ const cmd = line.querySelector('.cmd') as HTMLElement | null;
+ if (!cmd) return resolve();
+ line.classList.add('is-typing');
+ const text = cmd.dataset.text || '';
+ const proxy = { i: 0 };
+ gsap.to(proxy, {
+ i: text.length,
+ duration: Math.max(0.5, text.length * 0.025),
+ ease: 'none',
+ onUpdate: () => {
+ cmd.textContent = text.slice(0, Math.floor(proxy.i));
+ },
+ onComplete: () => {
+ cmd.textContent = text;
+ line.classList.remove('is-typing');
+ resolve();
+ },
+ });
+ }
+ });
+
+ let started = false;
+ ScrollTrigger.create({
+ trigger: body,
+ start: 'top 80%',
+ once: true,
+ onEnter: async () => {
+ if (started) return;
+ started = true;
+ for (const line of lines) {
+ await playLine(line);
+ await new Promise((r) => setTimeout(r, 200));
+ }
+ },
+ });
+ }
+ }
+
+ /* ─── section title split-word reveal ─── */
+ document.querySelectorAll('[data-split]').forEach((title) => {
+ const html = title.innerHTML;
+ const wrapped = html.replace(/(<[^>]+>)|([^<\s]+)/g, (m, tag, word) => {
+ if (tag) return tag;
+ return `${word} `;
+ });
+ title.innerHTML = wrapped;
+ const inners = title.querySelectorAll('.swi');
+ gsap.set(title.querySelectorAll('.sw'), {
+ display: 'inline-block',
+ overflow: 'hidden',
+ verticalAlign: 'bottom',
+ paddingBottom: '0.06em',
+ });
+ gsap.set(inners, { display: 'inline-block', yPercent: 110, opacity: 0 });
+ gsap.to(inners, {
+ yPercent: 0,
+ opacity: 1,
+ duration: 0.85,
+ ease: 'power3.out',
+ stagger: 0.04,
+ scrollTrigger: { trigger: title, start: 'top 88%', toggleActions: 'play none none none' },
+ });
+ });
+
+ if (!reduceMotion) gsap.ticker.lagSmoothing(500, 16);
+
+ return () => {
+ // tear down ScrollTriggers and tweens for clean re-init on route change
+ ScrollTrigger.getAll().forEach((t) => t.kill());
+ marqueeTween?.kill();
+ spotlightCleanup?.();
+ };
+ }, [pathname]);
+
+ return null;
+}
diff --git a/components/Marquee.tsx b/components/Marquee.tsx
new file mode 100644
index 0000000..ce63d5f
--- /dev/null
+++ b/components/Marquee.tsx
@@ -0,0 +1,20 @@
+import { Fragment } from 'react';
+
+interface Props {
+ items: string[];
+}
+
+export default function Marquee({ items }: Props) {
+ return (
+
+
+ {items.map((item, i) => (
+
+ {item}
+ ·
+
+ ))}
+
+
+ );
+}
diff --git a/components/Nav.tsx b/components/Nav.tsx
new file mode 100644
index 0000000..8e6bfc9
--- /dev/null
+++ b/components/Nav.tsx
@@ -0,0 +1,114 @@
+import Link from 'next/link';
+import RadarMark from './RadarMark';
+
+export type NavPage = 'studio' | 'recon' | 'pulse' | 'helm';
+
+interface Props {
+ active: NavPage;
+}
+
+const ArrowIcon = () => (
+
+
+
+);
+
+export default function Nav({ active }: Props) {
+ const isStudio = active === 'studio';
+ const denim = active !== 'recon';
+ const brandLabel = isStudio ? '42nights' : `42nights / ${active}`;
+ const ctaLabel = isStudio ? 'Book a discovery' : 'Book a deploy';
+ const ctaHref = isStudio ? '#contact' : '#cta';
+
+ return (
+
+
+
+
+
+
+ {brandLabel}
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/RadarMark.tsx b/components/RadarMark.tsx
new file mode 100644
index 0000000..9591c0c
--- /dev/null
+++ b/components/RadarMark.tsx
@@ -0,0 +1,23 @@
+interface Props {
+ size?: number;
+ withPad?: boolean;
+}
+
+export default function RadarMark({ size = 18, withPad = true }: Props) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/components/Terminal.tsx b/components/Terminal.tsx
new file mode 100644
index 0000000..824f3a9
--- /dev/null
+++ b/components/Terminal.tsx
@@ -0,0 +1,40 @@
+interface Line {
+ prompt: string; // e.g. '$' or '▶'
+ cmd?: string; // typed command (data-typed)
+ out?: string; // output line text (no command)
+ outClass?: 'ok' | 'muted';
+}
+
+interface Props {
+ title: string;
+ lines: Line[];
+}
+
+export default function Terminal({ title, lines }: Props) {
+ return (
+
+
+
+
+
+ {title}
+
+
+ {lines.map((line, i) =>
+ line.cmd ? (
+
+ {line.prompt}
+
+ ) : (
+
+ {line.out}
+
+ )
+ )}
+
+
+ );
+}
diff --git a/helm.html b/helm.html
deleted file mode 100644
index d9263a7..0000000
--- a/helm.html
+++ /dev/null
@@ -1,343 +0,0 @@
-
-
-
-
-
- Helm — the portfolio operating cockpit, deployed by 42nights
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- helm
-
-
-
-
- Helm · Built on Recon · Deployed by 42nights
-
-
-
- A portfolio
- cockpit for
- holdcos & PE.
-
-
- KPI roll-ups across portcos, variance flags, DD workspace, board-pack agent. On infrastructure you control. Deployed by 42nights in four weeks.
-
-
Operating partners · COOs · Holdcos · Family offices · LP teams
-
-
-
-
-
0 % on your own infra
-
-
0 PE skill packs
-
-
0 week deploy
-
-
-
-
-
- QuickBooks ·
- Xero ·
- NetSuite ·
- Stripe ·
- Plaid ·
- Rippling ·
- Gusto ·
- HubSpot ·
- Salesforce ·
- Carta ·
- Drive · Box · Docsend ·
- Notion ·
-
-
-
-
-
-
- 01
-
-
-
What Helm does
-
- From portcoto board pack.
-
-
-
-
-
-
-
-
-
-
-
- portcos
-
-
-
- holdco
-
- KPI roll-ups
- Portcos in. Dashboard out.
-
-
-
-
-
- plan
- actual
-
-
-
-
- flag
-
- Variance vs plan
- Flagged. Drafted. Reviewed.
-
-
-
-
-
- data room
-
- CIM
-
- model
-
- notes
-
-
- DD workspace
-
- checklist
-
- risk flags
-
- DD workspace generator
- Per-deal. Auto-tagged.
-
-
-
-
-
-
-
-
-
-
-
- notes
-
-
-
- BOARD
- PACK
- board ready
-
- Board-pack agent
- Notes in. Pack out.
-
-
-
-
-
-
-
- 02
-
-
-
Roadmap
-
- Five phasesper deployment.
-
-
-
-
-
-
-
-
01
-
02
-
03
-
04
-
05
-
-
-
-
-
-
Phase 1 · Helm
-
Multi-entity foundation
-
Workspace-as-portfolio-company. HoldCo workspace aggregating read-only views. Entities: Company, People, Contracts, KPIs, Reporting periods.
-
-
-
Phase 2 · Helm
-
Operating cadence
-
QuickBooks / Xero / NetSuite, Stripe, HRIS, Hub/Salesforce ingest. Auto-populated monthly & quarterly templates with variance flags.
-
-
-
Phase 3 · Helm
-
Diligence & deal flow
-
Deal pipeline as a first-class object. Per-deal DD workspace generator. Data-room ingest with auto-tagging and risk flag agents.
-
-
-
Phase 4 · Helm
-
Portfolio agents
-
Operating-partner agent per portco. Cross-portfolio benchmarking on margin, CAC payback, NRR percentiles. Talent-network agent.
-
-
-
Phase 5 · Helm
-
LP & governance
-
LP reporting view (capital account, IRR/MOIC). Board-pack generator from operating-partner output. SOC2-ready audit trail.
-
-
-
-
-
-
-
-
-
-
-
-
- 03
-
-
-
-
Get Helm deployed
-
- Four weeks toa holdco cockpit.
-
-
- 42nights deploys Helm inside your perimeter — one workspace per portco, holdco view rolling up. Discovery to running in four weeks.
-
-
-
-
-
-
-
- helm · pinemark-holdco
-
-
-
$
-
▲ holdco workspace ready · 7 portcos scoped
-
$
-
✓ QB · Stripe · HRIS · 14 KPIs synced
-
$
-
◇ 3 portcos flagged · 12 commentary drafts
-
$
-
◇ pack drafted · 38 pages · awaiting review
-
-
-
-
-
-
-
-
-
-
diff --git a/index.html b/index.html
deleted file mode 100644
index 7602026..0000000
--- a/index.html
+++ /dev/null
@@ -1,376 +0,0 @@
-
-
-
-
-
- 42nights — we deploy AI operating layers inside your perimeter
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 42n
-
-
-
-
- Engineering studio · SF + on-site · est. ’24
-
-
-
- We embed
- inside your team
- and ship the
- operating layer
- that runs your business.
-
-
-
- 42nights is a four-week, on-site engineering studio. We deploy Recon — and its variants Pulse for revenue teams and Helm for portfolio teams — inside your perimeter, then hand the keys to your operators.
-
-
-
-
-
-
0 week engagement
-
-
0 % on your infra
-
-
0 products deployed
-
-
0 day post-deploy support
-
-
-
-
-
- Holding companies ·
- Series B → late-stage ·
- RevOps ·
- Operating partners ·
- Family offices ·
- Holdcos ·
- Sales leadership ·
- COOs ·
- Studio of three ·
- SF + on-site ·
-
-
-
-
-
-
- 01
-
-
-
Approach
-
- Four weeks. On site. Then it's yours.
-
-
- We don't write a deck and disappear. We sit at your desk, watch your operators work, and ship the system that fits — then we leave.
-
-
-
-
-
- W1
-
-
Discovery on the ground.
-
One week shadowing your team. We map workflows, identify which pieces of Recon (or Pulse / Helm) fit, and write the deployment plan with you.
-
- scope & plan
-
-
- W2
-
-
Deploy the substrate.
-
Recon goes inside your VPC, on-prem, or air-gapped — whichever your security review demands. Identity, vault, gateway, audit log, all live by end of week.
-
- platform live
-
-
- W3
-
-
Ship the surface.
-
Pulse for revenue, Helm for portfolio, or a custom variant. Your data is wired, your skills are tuned, your operators are running real workflows by Friday.
-
- surface running
-
-
- W4
-
-
Hand off.
-
Train your team, document the deploy, transfer source escrow, set up the 90-day support cadence. We leave on Friday. The platform stays.
-
- team owns it
-
-
-
-
-
-
-
- 02
-
-
-
What we ship
-
- One platform.Two specialized cockpits.
-
-
- We built Recon ourselves. Pulse and Helm wrap it for revenue and portfolio teams respectively. We deploy whichever fits — and we'll fork a custom variant if neither does.
-
-
-
-
-
-
-
-
-
- 03
-
-
-
Why 42nights
-
- Operator-led. Source-available. On-site.
-
-
-
-
-
- ①
-
-
We built the thing we deploy.
-
Recon is ours. When something doesn't fit your stack, we change the substrate, not the contract.
-
-
-
- ②
-
-
Sovereignty is the default, not an upgrade.
-
Single-tenant by definition. Your VPC, your keys, your audit log. We pass your security review on day one.
-
-
-
- ③
-
-
Hands-on, not advisory.
-
We deploy and operate alongside your team — not in a slide deck from a thousand miles away. The handoff is real.
-
-
-
-
-
-
-
-
- 04
-
-
-
Engagement
-
- Fixed-fee. In person. Source escrow.
-
-
-
-
-
-
Format
- Four-week minimum, fully on-site at your office. Two engineers from us, embedded with your operators.
-
-
-
Fee
- Fixed-fee per engagement, scoped in week one. No hourly billing, no out-of-scope creep.
-
-
-
IP
- Source escrow at deploy. Custom skill packs are yours. Recon core stays under our license, available to you in perpetuity for that deployment.
-
-
-
Support
- Ninety days post-deploy on retainer — priority Slack, weekly check-ins, hot-fixes within 24 hours.
-
-
-
Capacity
- Three active engagements at a time, max. We're a studio, not a body shop.
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/main.js b/main.js
deleted file mode 100644
index 8c49aec..0000000
--- a/main.js
+++ /dev/null
@@ -1,482 +0,0 @@
-/* ═══════════════════════════════════════════════════════════
- recon · landing · GSAP · v2 (parallax + phase transition)
- Easings follow Emil's playbook: strong custom curves, ≤300ms UI.
- ═══════════════════════════════════════════════════════════ */
-
-(() => {
- const start = () => {
- if (typeof gsap === 'undefined' || typeof ScrollTrigger === 'undefined') {
- requestAnimationFrame(start);
- return;
- }
- init();
- };
- document.addEventListener('DOMContentLoaded', start);
-
- const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
- const EASE_OUT = 'cubic-bezier(0.23, 1, 0.32, 1)';
-
- function init() {
- gsap.registerPlugin(ScrollTrigger);
- document.body.classList.add('ready');
-
- heroSpotlight();
- parallax();
- heroTitleReveal();
- revealOnScroll();
- staggerGroups();
- counters();
- marquee();
- navBehavior();
- magneticButtons();
- productGlow();
- roadmapTabs();
- phaseToggle();
- terminalTypewriter();
- splitTitles();
-
- if (!reduceMotion) gsap.ticker.lagSmoothing(500, 16);
- }
-
- /* ─────── spotlight (mouse-follow) ─────── */
- function heroSpotlight() {
- if (reduceMotion) return;
- const el = document.querySelector('.spotlight');
- if (!el) return;
- gsap.set(el, { xPercent: -50, yPercent: -50, top: 0, left: 0 });
- const xTo = gsap.quickTo(el, 'x', { duration: 0.7, ease: 'power3.out' });
- const yTo = gsap.quickTo(el, 'y', { duration: 0.7, ease: 'power3.out' });
- xTo(window.innerWidth / 2); yTo(window.innerHeight / 3);
- window.addEventListener('pointermove', (e) => {
- xTo(e.clientX); yTo(e.clientY);
- }, { passive: true });
- }
-
- /* ─────── parallax (background layers, section numerals) ─────── */
- function parallax() {
- if (reduceMotion) return;
- document.querySelectorAll('[data-parallax]').forEach((el) => {
- const speed = parseFloat(el.dataset.parallax);
- if (!isFinite(speed) || speed <= 0) return;
- const trigger = el.closest('.section, .hero') || el.parentElement;
- gsap.to(el, {
- yPercent: -40 * speed,
- ease: 'none',
- scrollTrigger: {
- trigger,
- start: 'top bottom',
- end: 'bottom top',
- scrub: 0.4,
- },
- });
- });
- }
-
- /* ─────── hero title — defensive gsap.from() ───────
- gsap.from sets initial state and animates to natural CSS state.
- If the tween is interrupted, words fall back to visible. */
- function heroTitleReveal() {
- const scope = document.querySelector('.hero-phase.is-active') || document.querySelector('.hero');
- if (!scope) return;
- const words = scope.querySelectorAll('.hero-title .word');
- if (!words.length) return;
- gsap.from(words, {
- yPercent: 110,
- filter: 'blur(8px)',
- opacity: 0,
- duration: 0.95,
- ease: 'power4.out',
- stagger: 0.05,
- delay: 0.1,
- });
- }
-
- /* ─────── [data-reveal] scroll-triggered fade-up ─────── */
- function revealOnScroll() {
- document.querySelectorAll('[data-reveal]').forEach((el) => {
- gsap.fromTo(
- el,
- { opacity: 0, y: 22, filter: 'blur(6px)' },
- {
- opacity: 1, y: 0, filter: 'blur(0px)',
- duration: 0.8, ease: 'power3.out',
- scrollTrigger: { trigger: el, start: 'top 88%', toggleActions: 'play none none none' },
- }
- );
- });
- }
-
- /* ─────── grouped 50ms-stagger reveal (Emil-style) ─────── */
- function staggerGroups() {
- document.querySelectorAll('[data-stagger]').forEach((group) => {
- const items = Array.from(group.children);
- if (!items.length) return;
- gsap.set(items, { opacity: 0, y: 14 });
- gsap.to(items, {
- opacity: 1, y: 0,
- duration: 0.55,
- ease: 'cubic-bezier(0.23, 1, 0.32, 1)',
- stagger: 0.05,
- scrollTrigger: { trigger: group, start: 'top 88%', toggleActions: 'play none none none' },
- });
- });
- }
-
- /* ─────── counter animation on scroll ─────── */
- function counters() {
- document.querySelectorAll('[data-counter]').forEach((node) => {
- const target = parseFloat(node.dataset.counter);
- const proxy = { v: 0 };
- ScrollTrigger.create({
- trigger: node,
- start: 'top 90%',
- once: true,
- onEnter: () => {
- gsap.to(proxy, {
- v: target, duration: 1.4, ease: 'power3.out',
- onUpdate: () => { node.textContent = Math.round(proxy.v).toLocaleString(); },
- });
- },
- });
- });
- }
-
- /* ─────── marquee (gsap loop, slow on hover) ─────── */
- function marquee() {
- const track = document.querySelector('.marquee-track');
- if (!track) return;
- track.innerHTML += track.innerHTML;
- const distance = track.scrollWidth / 2;
- const tween = gsap.to(track, {
- x: -distance, duration: 40, ease: 'none', repeat: -1,
- });
- track.parentElement.addEventListener('mouseenter', () => tween.timeScale(0.1));
- track.parentElement.addEventListener('mouseleave', () => tween.timeScale(1));
- }
-
- /* ─────── nav: compact on scroll + sliding indicator + scroll-spy ─────── */
- function navBehavior() {
- const nav = document.querySelector('.nav');
- const links = document.querySelectorAll('.nav-link[data-nav]');
- const indicator = document.querySelector('.nav-indicator');
- const linksList = document.querySelector('.nav-links');
-
- ScrollTrigger.create({
- start: 'top -40',
- onUpdate: (self) => nav.classList.toggle('compact', self.scroll() > 40),
- });
-
- const moveTo = (el) => {
- if (!el || !indicator || !linksList) return;
- const r = el.getBoundingClientRect();
- const parent = linksList.getBoundingClientRect();
- gsap.to(indicator, {
- x: r.left - parent.left,
- y: r.top - parent.top,
- width: r.width,
- height: r.height,
- opacity: 1,
- duration: 0.4,
- ease: 'power3.out',
- });
- };
- links.forEach((l) => l.addEventListener('pointerenter', () => moveTo(l)));
- linksList?.addEventListener('pointerleave', () => {
- gsap.to(indicator, { opacity: 0, duration: 0.3 });
- });
-
- const sectionIds = ['#why', '#products', '#how', '#roadmap'];
- sectionIds.forEach((id) => {
- const sec = document.querySelector(id);
- if (!sec) return;
- ScrollTrigger.create({
- trigger: sec,
- start: 'top center',
- end: 'bottom center',
- onToggle: (self) => {
- if (!self.isActive) return;
- links.forEach((l) => l.classList.toggle('is-active', l.getAttribute('href') === id));
- const active = document.querySelector('.nav-link.is-active');
- if (active) moveTo(active);
- },
- });
- });
- }
-
- /* ─────── magnetic buttons ─────── */
- function magneticButtons() {
- if (reduceMotion) return;
- document.querySelectorAll('[data-magnetic]').forEach((el) => {
- const xTo = gsap.quickTo(el, 'x', { duration: 0.5, ease: 'elastic.out(1, 0.4)' });
- const yTo = gsap.quickTo(el, 'y', { duration: 0.5, ease: 'elastic.out(1, 0.4)' });
- el.addEventListener('pointermove', (e) => {
- const r = el.getBoundingClientRect();
- const dx = e.clientX - (r.left + r.width / 2);
- const dy = e.clientY - (r.top + r.height / 2);
- xTo(dx * 0.22); yTo(dy * 0.32);
- });
- el.addEventListener('pointerleave', () => { xTo(0); yTo(0); });
- });
- }
-
- /* ─────── product card glow follows pointer ─────── */
- function productGlow() {
- document.querySelectorAll('.product-card').forEach((card) => {
- card.addEventListener('pointermove', (e) => {
- const r = card.getBoundingClientRect();
- card.style.setProperty('--gx', ((e.clientX - r.left) / r.width) * 100 + '%');
- card.style.setProperty('--gy', ((e.clientY - r.top) / r.height) * 100 + '%');
- });
- });
- }
-
- /* ─────── roadmap (product tabs + horizontal phase timeline) ─────── */
- function roadmapTabs() {
- const btns = document.querySelectorAll('.rt-btn');
- const indicator = document.querySelector('.rt-indicator');
- const panelGroups = document.querySelectorAll('[data-roadmap-panel]');
- const dots = document.querySelectorAll('.rm-dot');
- const progress = document.querySelector('.rm-progress');
- const timeline = document.querySelector('.rm-timeline');
-
- const placeIndicator = (btn) => {
- const tabsRect = btn.parentElement.getBoundingClientRect();
- const r = btn.getBoundingClientRect();
- gsap.to(indicator, {
- x: r.left - tabsRect.left,
- y: r.top - tabsRect.top,
- width: r.width,
- height: r.height,
- duration: 0.42,
- ease: 'power3.out',
- });
- };
- const initial = document.querySelector('.rt-btn.active');
- if (initial) requestAnimationFrame(() => placeIndicator(initial));
-
- // place progress fill based on active phase
- const updateProgress = () => {
- if (!progress || !dots.length || !timeline) return;
- const track = timeline.querySelector('.rm-track');
- if (!track) return;
- const activeIdx = parseInt(timeline.dataset.activePhase || '0', 10);
- const pct = dots.length > 1 ? activeIdx / (dots.length - 1) : 0;
- const width = pct * (track.clientWidth - 32);
- gsap.to(progress, { width, duration: 0.55, ease: 'power3.out' });
- };
-
- const activePhase = (idx) => {
- timeline.dataset.activePhase = idx;
- dots.forEach((d, i) => d.classList.toggle('active', i === idx));
- // update visible panel within active group
- const visibleGroup = document.querySelector('.rm-panels.active');
- if (visibleGroup) {
- const panels = visibleGroup.querySelectorAll('.rm-panel');
- panels.forEach((p, i) => p.classList.toggle('active', i === idx));
- const target = panels[idx];
- if (target) {
- gsap.fromTo(target, { opacity: 0, y: 10, filter: 'blur(4px)' },
- { opacity: 1, y: 0, filter: 'blur(0px)', duration: 0.45, ease: 'power3.out' });
- }
- }
- updateProgress();
- };
-
- dots.forEach((dot, idx) => {
- dot.addEventListener('click', () => activePhase(idx));
- });
-
- btns.forEach((btn) => {
- btn.addEventListener('click', () => {
- btns.forEach((b) => { b.classList.remove('active'); b.setAttribute('aria-selected', 'false'); });
- btn.classList.add('active');
- btn.setAttribute('aria-selected', 'true');
- placeIndicator(btn);
-
- const target = btn.dataset.roadmap;
- panelGroups.forEach((g) => {
- const isTarget = g.dataset.roadmapPanel === target;
- g.classList.toggle('active', isTarget);
- if (isTarget) {
- const visiblePanel = g.querySelector('.rm-panel.active');
- if (visiblePanel) {
- gsap.fromTo(visiblePanel, { opacity: 0, y: 10, filter: 'blur(4px)' },
- { opacity: 1, y: 0, filter: 'blur(0px)', duration: 0.45, ease: 'power3.out' });
- }
- }
- });
- });
- });
-
- window.addEventListener('resize', () => {
- const active = document.querySelector('.rt-btn.active');
- if (active) placeIndicator(active);
- updateProgress();
- });
-
- // initial progress on load
- requestAnimationFrame(updateProgress);
- }
-
- /* ─────── phase toggle (hero GTM ↔ PE transition) ─────── */
- function phaseToggle() {
- const btns = document.querySelectorAll('.pt-btn');
- const indicator = document.querySelector('.pt-indicator');
- const phases = document.querySelectorAll('.hero-phase');
- if (!btns.length || !indicator) return;
-
- const placeIndicator = (btn) => {
- const tabsRect = btn.parentElement.getBoundingClientRect();
- const r = btn.getBoundingClientRect();
- gsap.to(indicator, {
- x: r.left - tabsRect.left,
- y: r.top - tabsRect.top,
- width: r.width,
- height: r.height,
- duration: 0.45,
- ease: 'power3.out',
- });
- };
-
- const initial = document.querySelector('.pt-btn.active');
- if (initial) requestAnimationFrame(() => placeIndicator(initial));
-
- let currentPhase = 'gtm';
- let switching = false;
-
- const switchTo = (target) => {
- if (target === currentPhase || switching) return;
- switching = true;
- const incoming = document.querySelector(`[data-phase-panel="${target}"]`);
- const outgoing = document.querySelector(`[data-phase-panel="${currentPhase}"]`);
-
- // body data-phase drives accent color on indicator etc.
- document.body.dataset.phase = target;
-
- // outgoing: blur out
- gsap.to(outgoing, {
- opacity: 0,
- y: -8,
- filter: 'blur(6px)',
- duration: 0.28,
- ease: 'power2.in',
- onComplete: () => {
- outgoing.classList.remove('is-active');
- outgoing.setAttribute('aria-hidden', 'true');
- },
- });
-
- // incoming: blur in (slightly delayed)
- incoming.classList.add('is-active');
- incoming.setAttribute('aria-hidden', 'false');
- gsap.fromTo(incoming,
- { opacity: 0, y: 8, filter: 'blur(6px)' },
- {
- opacity: 1, y: 0, filter: 'blur(0px)',
- duration: 0.46,
- delay: 0.18,
- ease: 'power3.out',
- }
- );
-
- // animate the words inside the new phase
- const words = incoming.querySelectorAll('.hero-title .word');
- gsap.fromTo(words,
- { yPercent: 90, filter: 'blur(8px)', opacity: 0 },
- {
- yPercent: 0, filter: 'blur(0px)', opacity: 1,
- duration: 0.7, ease: 'power4.out', stagger: 0.04,
- delay: 0.22,
- onComplete: () => { switching = false; },
- }
- );
-
- currentPhase = target;
- };
-
- btns.forEach((btn) => {
- btn.addEventListener('click', () => {
- btns.forEach((b) => { b.classList.remove('active'); b.setAttribute('aria-selected', 'false'); });
- btn.classList.add('active');
- btn.setAttribute('aria-selected', 'true');
- placeIndicator(btn);
- switchTo(btn.dataset.phaseBtn);
- });
- });
-
- window.addEventListener('resize', () => {
- const active = document.querySelector('.pt-btn.active');
- if (active) placeIndicator(active);
- });
- }
-
- /* ─────── terminal typewriter ─────── */
- function terminalTypewriter() {
- const body = document.getElementById('terminal-body');
- if (!body) return;
- const cmds = body.querySelectorAll('.cmd');
- const outs = body.querySelectorAll('.t-line.out');
- cmds.forEach((c) => { c.dataset.text = c.dataset.typed; c.textContent = ''; });
- outs.forEach((o) => gsap.set(o, { opacity: 0, height: 0, overflow: 'hidden' }));
- const lines = Array.from(body.children);
-
- const playLine = (line) => new Promise((resolve) => {
- if (line.classList.contains('out')) {
- gsap.to(line, { opacity: 1, height: 'auto', duration: 0.25, onComplete: resolve });
- } else {
- const cmd = line.querySelector('.cmd');
- if (!cmd) return resolve();
- line.classList.add('is-typing');
- const text = cmd.dataset.text || '';
- const proxy = { i: 0 };
- gsap.to(proxy, {
- i: text.length,
- duration: Math.max(0.5, text.length * 0.025),
- ease: 'none',
- onUpdate: () => { cmd.textContent = text.slice(0, Math.floor(proxy.i)); },
- onComplete: () => {
- cmd.textContent = text;
- line.classList.remove('is-typing');
- resolve();
- },
- });
- }
- });
-
- let started = false;
- ScrollTrigger.create({
- trigger: body,
- start: 'top 80%',
- once: true,
- onEnter: async () => {
- if (started) return;
- started = true;
- for (const line of lines) {
- await playLine(line);
- await new Promise((r) => setTimeout(r, 200));
- }
- },
- });
- }
-
- /* ─────── section title split-word reveal ─────── */
- function splitTitles() {
- document.querySelectorAll('[data-split]').forEach((title) => {
- const html = title.innerHTML;
- const wrapped = html.replace(/(<[^>]+>)|([^<\s]+)/g, (m, tag, word) => {
- if (tag) return tag;
- return `${word} `;
- });
- title.innerHTML = wrapped;
- const inners = title.querySelectorAll('.swi');
- gsap.set(title.querySelectorAll('.sw'), {
- display: 'inline-block', overflow: 'hidden', verticalAlign: 'bottom', paddingBottom: '0.06em',
- });
- gsap.set(inners, { display: 'inline-block', yPercent: 110, opacity: 0 });
- gsap.to(inners, {
- yPercent: 0, opacity: 1, duration: 0.85, ease: 'power3.out', stagger: 0.04,
- scrollTrigger: { trigger: title, start: 'top 88%', toggleActions: 'play none none none' },
- });
- });
- }
-})();
diff --git a/next-env.d.ts b/next-env.d.ts
new file mode 100644
index 0000000..830fb59
--- /dev/null
+++ b/next-env.d.ts
@@ -0,0 +1,6 @@
+///
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/next.config.mjs b/next.config.mjs
new file mode 100644
index 0000000..4f2de35
--- /dev/null
+++ b/next.config.mjs
@@ -0,0 +1,17 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: true,
+ poweredByHeader: false,
+ async headers() {
+ return [
+ {
+ source: '/(.*)\\.(css|js|svg|woff2)',
+ headers: [
+ { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' },
+ ],
+ },
+ ];
+ },
+};
+
+export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..4f71878
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,1030 @@
+{
+ "name": "42nights-landing",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "42nights-landing",
+ "version": "0.1.0",
+ "dependencies": {
+ "gsap": "^3.12.5",
+ "next": "^15.1.7",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.5",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "typescript": "^5.7.3"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.16.tgz",
+ "integrity": "sha512-9QMKolCl+JnJtaRAQSXy4RQrhgfe8W7/G1+Hl3QSB/HZY7zQMzTwPDdTRwwio8BS96ps1MHpHhbS8qxoNV3JIQ==",
+ "license": "MIT"
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.16.tgz",
+ "integrity": "sha512-wzdER4JZj+31vNkhaZ1Ght3IsNI8DMwj7VqadfIOqJB5sh8FiOqNSopYADQn6mgEPomzDd/DHqBcfo2fmVMYtg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.16.tgz",
+ "integrity": "sha512-PPTo+cvcanxkuDEuDyZGk28ntmu0WjfkxqlG7hw9Mhsiribs4x1C6h2Culn0cJKqsne1gFjjZRK3ax7WYlSxgg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.16.tgz",
+ "integrity": "sha512-Jl0IL9P7S8uNl5oI1TqrQmfmLp7OqjWM58000pVnUVIsHrvPP6m9QDW/uNWYUbmd+8IYvc6MTeZKICstBMBpew==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.16.tgz",
+ "integrity": "sha512-Zf0BIqv/o5uOWfyRkzgGhyV2Tky7HLt0bG+w7XWdaU1JpyX0tltM3TrSfa/Y9c597SJG4CzN47+u2InhgZZ4vg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.16.tgz",
+ "integrity": "sha512-HCDDU1TRLeUDV180QQTWrs5Oa4lIcI7XH9nF0UVUVmYLN/boZ6LqyFtm3814gc1fv+lOVyKaw5B6bVC9BpXTSQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.16.tgz",
+ "integrity": "sha512-kvXUY1dn5wxKuMkXxQRUbPjEnKxW1PR9uKOm0zpIpj3574+cFfaePhYFmBVtrOuwt+w34OdDzNaJr5Iixf+HBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.16.tgz",
+ "integrity": "sha512-zpOQuF+eyENMXRjglp2hZCIrUjTdO37suEBnDn1mX4PXSuetXZDMLpjKOh4dYSw3SiDTnOoOUwBl5i5Elr6nnQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.16.tgz",
+ "integrity": "sha512-LnwKYpiSmIzXlTq76hMeeIzZoDcFwu848p6H+QBkGFJIbZphgzNUPdHruJcHM/bFnaFeco0l1Frie5I27VKglA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "22.19.17",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
+ "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001792",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz",
+ "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/gsap": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz",
+ "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==",
+ "license": "Standard 'no charge' license: https://gsap.com/standard-license."
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/next": {
+ "version": "15.5.16",
+ "resolved": "https://registry.npmjs.org/next/-/next-15.5.16.tgz",
+ "integrity": "sha512-aZExBk/V6JCu3NCFc90twdj9L/M3y0+ukeQwUAZbOiqRhAX+h2oMEa0NZFhcpj6HYRYjVS3V2/3xvyOpNnmw7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "15.5.16",
+ "@swc/helpers": "0.5.15",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "15.5.16",
+ "@next/swc-darwin-x64": "15.5.16",
+ "@next/swc-linux-arm64-gnu": "15.5.16",
+ "@next/swc-linux-arm64-musl": "15.5.16",
+ "@next/swc-linux-x64-gnu": "15.5.16",
+ "@next/swc-linux-x64-musl": "15.5.16",
+ "@next/swc-win32-arm64-msvc": "15.5.16",
+ "@next/swc-win32-x64-msvc": "15.5.16",
+ "sharp": "^0.34.3"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.51.1",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
+ "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.6",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
+ "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.6"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..175bcbd
--- /dev/null
+++ b/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "42nights-landing",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "^15.1.7",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "gsap": "^3.12.5"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.5",
+ "@types/react": "^19.0.8",
+ "@types/react-dom": "^19.0.3",
+ "typescript": "^5.7.3"
+ }
+}
diff --git a/favicon.svg b/public/favicon.svg
similarity index 100%
rename from favicon.svg
rename to public/favicon.svg
diff --git a/robots.txt b/public/robots.txt
similarity index 100%
rename from robots.txt
rename to public/robots.txt
diff --git a/sitemap.xml b/public/sitemap.xml
similarity index 100%
rename from sitemap.xml
rename to public/sitemap.xml
diff --git a/pulse.html b/pulse.html
deleted file mode 100644
index 7736214..0000000
--- a/pulse.html
+++ /dev/null
@@ -1,338 +0,0 @@
-
-
-
-
-
- Pulse — the GTM operating cockpit, deployed by 42nights
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- pulse
-
-
-
-
- Pulse · Built on Recon · Deployed by 42nights
-
-
-
- The operating layer
- between signal
- and revenue.
-
-
- Identity-resolved CRM, inbox-first sync, scheduled agents, forecast that ties to source. On infrastructure you control. Deployed by 42nights in four weeks.
-
-
RevOps · Sales leadership · Growth ops · CROs
-
-
-
-
-
0 % on your own infra
-
-
0 GTM skill packs
-
-
0 week deploy
-
-
-
-
-
- Salesforce ·
- HubSpot ·
- Apollo ·
- Clay ·
- Gmail ·
- Outlook ·
- Calendar ·
- LinkedIn ·
- PostHog ·
- Smartlead ·
- Instantly ·
- Slack ·
-
-
-
-
-
-
- 01
-
-
-
What Pulse does
-
- From inboxto closed-won.
-
-
-
-
-
-
-
- maya@northstar.io
-
- m.adams@northstar.io
-
- li/maya-adams
-
-
-
-
- M. Adams
-
- Identity-resolved CRM
- Three channels. One row.
-
-
-
-
-
-
- trigger
-
-
- RUN
-
-
- DIFF
- review
-
- Per-account next-best-action
- Trigger → run → diff back.
-
-
-
-
-
- posthog
-
- apollo
-
- clearbit
-
- webhook
-
- linkedin
-
-
- signals
-
- One surface for intent
- Many sources. One stream.
-
-
-
-
-
-
-
-
-
-
-
- forecast
-
- Forecast that ties to source
- Stage · owner · segment.
-
-
-
-
-
-
-
- 02
-
-
-
Roadmap
-
- Five phasesper deployment.
-
-
-
-
-
-
-
-
01
-
02
-
03
-
04
-
05
-
-
-
-
-
-
Phase 1 · Pulse
-
Pipeline foundations
-
Canonical People + Company model. Inbox-first sync. Calendar → meeting → deal attribution. Lifecycle stage, owner, last touch.
-
-
-
Phase 2 · Pulse
-
Signal capture
-
Apollo / Clay enrichment. PostHog event timeline. LinkedIn + outbound capture. Intent adapters behind one signals surface.
-
-
-
Phase 3 · Pulse
-
Agentic execution
-
Lead triage · ICP scoring · outbound draft · meeting prep · pipeline review · churn flag. Per-account next-best-action on a schedule.
-
-
-
Phase 4 · Pulse
-
Revenue analytics
-
Weighted forecast by stage / owner / segment. Source → meeting → opp attribution. Cohort retention via PostHog or warehouse adapter.
-
-
-
Phase 5 · Pulse
-
Multiplayer
-
Team workspaces with row-level RBAC. Shared skill libraries with review/approval. Slack / Linear two-way sync for handoffs.
-
-
-
-
-
-
-
-
-
-
-
-
- 03
-
-
-
-
Get Pulse deployed
-
- Four weeks toyour own pipeline OS.
-
-
- 42nights deploys Pulse inside your perimeter. Discovery on Monday, your team running it on the Friday of week four.
-
-
-
-
-
-
-
- pulse · acme-prod
-
-
-
$
-
▲ workspace ready · auth via SSO
-
$
-
✓ 4,212 contacts · 318 active deals indexed
-
$
-
✓ 47 deals scored · 3 flagged for risk
-
$
-
◇ Q2 weighted: $2.4M · vs plan: +112%
-
-
-
-
-
-
-
-
-
-
diff --git a/recon.html b/recon.html
deleted file mode 100644
index 1fc5cd7..0000000
--- a/recon.html
+++ /dev/null
@@ -1,620 +0,0 @@
-
-
-
-
-
- Recon — the operating substrate for revenue and portfolio teams
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- recon
-
-
-
-
- Self-hosted · Single-tenant · Audit-ready
-
-
-
- The operating substrate
- for ops-heavy teams.
-
-
- Recon is the self-hosted gateway, workspace, and skill runtime that Pulse and Helm are built on. Deployed by 42nights inside your perimeter.
-
-
RevOps · Sales leadership · Operating partners · COOs · Family offices
-
-
-
-
-
0 % on your own infra
-
-
0 product surfaces
-
-
0 vendor lock-in
-
-
-
-
-
- Salesforce ·
- HubSpot ·
- Gmail ·
- Outlook ·
- Slack ·
- Linear ·
- QuickBooks ·
- Stripe ·
- NetSuite ·
- Xero ·
- Apollo ·
- Clay ·
- PostHog ·
- Notion ·
- Drive ·
- Box ·
- MCP servers ·
-
-
-
-
-
-
- 01
-
-
-
Why operators choose Recon
-
- Your data. Your perimeter. Your agents.
-
-
-
-
-
-
-
- Recon stays inside your perimeter; connectors call out, never in
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
YOUR PERIMETER
-
VPC · laptop · air-gapped
-
-
-
-
Pulse
-
GTM workspace
-
-
-
Helm
-
Portfolio cockpit
-
-
-
-
-
Gateway
-
audit log · vault · skill runtime
-
-
-
-
-
-
-
Salesforce · HubSpot
-
Stripe · QuickBooks · Xero
-
Gmail · Outlook · Calendar
-
Apollo · Clay · PostHog
-
MCP servers · LLM APIs
-
-
-
- ✕
- no inbound
-
-
- → outbound only
-
-
- audit log signed
-
-
- your keys, your network
-
-
-
-
-
-
-
-
-
- 02
-
-
-
Two products · one substrate
-
- Pulse for revenue.Helm for portfolios.
-
-
- Both wrap the same self-hosted core — same gateway, same identity vault, same skill substrate. Different operators, very different jobs to be done.
-
-
-
-
-
-
-
-
-
Pulse
-
GTM operating system
-
-
- The operating layer between raw signal and revenue execution.
-
- Identity-resolved CRM, inbox-first sync, Apollo / Clay-style enrichment, scheduled outbound, attribution and forecast — all on infrastructure you control.
-
-
-
-
-
-
-
-
-
-
Helm
-
Portfolio operating cockpit
-
-
- A holdco cockpit — every portco a workspace, rolling up to one view.
-
- KPI ingest from QuickBooks, Stripe, HRIS, HubSpot. Variance flags with agent-drafted commentary. Per-deal DD workspace generator. Cross-portfolio benchmarking on margin, CAC payback, NRR.
-
-
-
-
-
-
-
-
-
-
- 03
-
-
-
How it deploys
-
- Three layers. One process tree.
-
-
-
-
-
-
Shells
-
- apps/web
- Pulse
- Helm
-
-
-
-
-
Gateway
-
- profile-managed daemon
- audit signer
- credential vault
-
-
-
-
-
Extensions
-
- AI gateway
- Identity
- Composio
- Apollo
- PostHog
-
-
-
-
- macOS
- Linux
- Docker
- VPC
- Air-gapped
-
-
-
-
-
-
-
- 04
-
-
-
Capabilities
-
- What it does, out of the box.
-
-
-
-
-
-
-
- maya@northstar.io
-
- m.adams@northstar.io
-
- li/maya-adams
-
-
-
-
- M. Adams
-
- Identity-resolved CRM
- Three channels. One row.
-
-
-
-
-
-
- trigger
-
-
- RUN
-
-
- DIFF
- review
-
- Skills act on schedule
- Trigger → run → diff back.
-
-
-
-
-
- posthog
-
- apollo
-
- clearbit
-
- webhook
-
- linkedin
-
-
- signals
-
- One surface for intent
- Many sources. One stream.
-
-
-
-
-
-
-
-
-
- portcos
-
-
-
- holdco
-
- Holdco KPI roll-ups
- Portcos in. Dashboard out.
-
-
-
-
-
-
-
- 05
-
-
-
Roadmap
-
- Five phases. Two products. One substrate.
-
-
-
-
- Pulse — GTM
- Helm — PE
-
-
-
-
-
-
-
-
01
-
02
-
03
-
04
-
05
-
-
-
-
-
-
Phase 1 · Pulse
-
Pipeline foundations
-
Canonical People + Company model. Inbox-first sync. Calendar → meeting → deal attribution. Lifecycle stage, owner, last touch.
-
-
-
Phase 2 · Pulse
-
Signal capture
-
Apollo / Clay enrichment. PostHog event timeline. LinkedIn + outbound capture. Intent adapters behind one signals surface.
-
-
-
Phase 3 · Pulse
-
Agentic execution
-
Lead triage · ICP scoring · outbound draft · meeting prep · pipeline review · churn flag. Per-account next-best-action on a schedule.
-
-
-
Phase 4 · Pulse
-
Revenue analytics
-
Weighted forecast by stage / owner / segment. Source → meeting → opp attribution. Cohort retention via PostHog or warehouse adapter.
-
-
-
Phase 5 · Pulse
-
Multiplayer
-
Team workspaces with row-level RBAC. Shared skill libraries with review/approval. Slack / Linear two-way sync for handoffs.
-
-
-
-
-
-
Phase 1 · Helm
-
Multi-entity foundation
-
Workspace-as-portfolio-company. HoldCo workspace aggregating read-only views. Entities: Company, People, Contracts, KPIs, Reporting periods.
-
-
-
Phase 2 · Helm
-
Operating cadence
-
QuickBooks / Xero / NetSuite, Stripe, HRIS, Hub/Salesforce ingest. Auto-populated monthly & quarterly templates with variance flags.
-
-
-
Phase 3 · Helm
-
Diligence & deal flow
-
Deal pipeline as a first-class object. Per-deal DD workspace generator. Data-room ingest with auto-tagging and risk flag agents.
-
-
-
Phase 4 · Helm
-
Portfolio agents
-
Operating-partner agent per portco. Cross-portfolio benchmarking on margin, CAC payback, NRR percentiles. Talent-network agent.
-
-
-
Phase 5 · Helm
-
LP & governance
-
LP reporting view (capital account, IRR/MOIC). Board-pack generator from operating-partner output. SOC2-ready audit trail.
-
-
-
-
-
-
-
-
-
- 06
-
-
-
Trust
-
- Built to passyour security review.
-
-
-
- Single-tenant by definition. Bring your own keys for LLM, CRM, storage, and identity. SOC2-ready audit log per workspace, per skill, per credential — pipe it straight to your SIEM. Zero telemetry, verifiable in source provided under your contract. Workspace-scoped RBAC isolates your sales workspace from your portfolio data, by default.
-
-
-
-
-
-
-
- 07
-
-
-
-
Get started
-
- Run it where yourdata already lives.
-
-
- We deploy Recon inside your perimeter and stay close through onboarding, integration, and skill-pack tuning. Talk to us about scope, security, and pricing.
-
-
-
-
-
-
-
- recon · acme-prod
-
-
-
$
-
▲ workspace ready · auth via SSO
-
$
-
✓ 4,212 contacts · 318 active deals indexed
-
$
-
✓ 47 deals scored · 3 flagged for risk
-
$
-
◇ 142 agent actions · 0 anomalies
-
-
-
-
-
-
-
-
-
-
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..bde17a6
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [{ "name": "next" }],
+ "paths": { "@/*": ["./*"] }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/vercel.json b/vercel.json
deleted file mode 100644
index eff36ae..0000000
--- a/vercel.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "cleanUrls": true,
- "trailingSlash": false,
- "headers": [
- {
- "source": "/(.*)\\.(css|js|svg|png|jpg|jpeg|webp|woff2)",
- "headers": [
- { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
- ]
- },
- {
- "source": "/(.*)\\.html",
- "headers": [
- { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
- ]
- }
- ]
-}