Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 141 additions & 36 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@
letter-spacing: -0.01em;
color: var(--ink);
}
.brand img { width: 32px; height: 32px; }
.brand img { width: 44px; height: 44px; }

.nav-links {
display: flex;
Expand Down Expand Up @@ -246,7 +246,7 @@
}
@media (max-width: 480px) {
.brand { font-size: 18px; gap: 10px; }
.brand img { width: 28px; height: 28px; }
.brand img { width: 34px; height: 34px; }
.nav-cta { height: 36px; padding-inline: 14px; font-size: 11px; letter-spacing: 0.08em; }
}

Expand All @@ -263,7 +263,7 @@
border-radius: 999px;
background: var(--status);
}
.hero h1 { max-width: 18ch; margin-top: 20px; }
.hero h1 { max-width: 18ch; margin-top: 0; }
.hero-lede { margin-top: 28px; max-width: 56ch; }
.hero-actions {
margin-top: 40px;
Expand Down Expand Up @@ -332,28 +332,17 @@
}
.demo-iframe {
width: 100%;
height: clamp(440px, 64vh, 680px);
height: clamp(600px, 82vh, 880px);
border: 0;
}
.demo-meta {
display: flex;
align-items: center;
justify-content: space-between;
justify-content: flex-end;
margin-top: 20px;
gap: 16px;
flex-wrap: wrap;
}
.demo-meta-left {
display: inline-flex;
align-items: center;
gap: 12px;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-3);
}
.demo-meta-left .dot { width: 6px; height: 6px; border-radius: 999px; background: var(--status); }

/* ───── Quickstart with code tabs ───── */

Expand Down Expand Up @@ -808,7 +797,55 @@
margin-bottom: 6px;
}
.whitepaper-meta dd { margin: 0; font-size: 14px; color: var(--ink-inverse); }
.whitepaper-actions { margin-top: 28px; display: flex; flex-wrap: wrap; gap: 14px; }
.wp-form { margin-top: 28px; }
.wp-form-label {
display: block;
font-size: 11px;
letter-spacing: 0.18em;
text-transform: uppercase;
font-weight: 500;
color: var(--ink-inverse-2);
margin-bottom: 10px;
}
.wp-form-row {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: stretch;
}
.wp-form input {
flex: 1 1 240px;
height: 48px;
padding: 0 16px;
background: transparent;
border: 1px solid var(--line-inverse);
color: var(--ink-inverse);
font-size: 14px;
font-family: var(--font-body);
border-radius: 0;
outline: none;
transition: border-color 180ms ease;
}
.wp-form input::placeholder { color: var(--ink-inverse-2); opacity: 0.7; }
.wp-form input:focus { border-color: var(--ink-inverse); }
.wp-form-submit { white-space: nowrap; }
.wp-form-note {
margin-top: 12px;
font-family: var(--font-body);
font-size: 12px;
color: var(--ink-inverse-2);
}
.wp-form-note.error { color: #fca5a5; }
.wp-success h4 {
font-family: var(--font-display);
font-weight: 400;
font-variation-settings: "opsz" 60;
font-size: 1.5rem;
letter-spacing: -0.02em;
margin-bottom: 8px;
color: var(--ink-inverse);
}
.wp-success p { color: var(--ink-inverse-2); }

/* ───── Footer ───── */

Expand Down Expand Up @@ -845,11 +882,23 @@
flex-wrap: wrap;
gap: 14px 28px;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: var(--ink-3);
}
.footer-bottom .status { display: inline-flex; align-items: center; gap: 8px; }
.footer-bottom .status .dot { width: 6px; height: 6px; border-radius: 999px; background: var(--status); }
.footer-socials { display: inline-flex; align-items: center; gap: 12px; }
.footer-socials a {
display: inline-flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border: 1px solid var(--line);
color: var(--ink-2);
transition: color 180ms ease, border-color 180ms ease, background 180ms ease;
}
.footer-socials a:hover { color: var(--bg); background: var(--ink); border-color: var(--ink); }
.footer-socials svg { width: 16px; height: 16px; }
</style>
</head>

Expand Down Expand Up @@ -882,10 +931,6 @@
<!-- Hero -->
<section class="hero">
<div class="container">
<div class="hero-eyebrow eyebrow">
<span class="dot" aria-hidden="true"></span>
ZeroAuth · Live in production
</div>
<h1 class="display">
Authentication where a breach exposes <em>nothing</em>.
</h1>
Expand Down Expand Up @@ -938,10 +983,6 @@ <h2 class="display-2">A signup, <em>end&nbsp;to&nbsp;end</em>.</h2>
</div>

<div class="demo-meta">
<span class="demo-meta-left">
<span class="dot" aria-hidden="true"></span>
Running locally · No real biometric collected
</span>
<a href="/demo.html" class="link" target="_blank" rel="noopener">Open fullscreen <span aria-hidden="true">↗</span></a>
</div>
</div>
Expand Down Expand Up @@ -1386,11 +1427,26 @@ <h2>The cryptographic <em>foundation</em>.</h2>
</div>
</dl>

<div class="whitepaper-actions">
<a href="/docs/whitepaper.pdf" class="btn on-dark" download>
Download white paper <span class="arrow" aria-hidden="true">↓</span>
</a>
<a href="/docs/" class="btn ghost on-dark">View as HTML</a>
<form id="wpForm" class="wp-form" novalidate>
<label for="wpEmail" class="wp-form-label">Email it to me</label>
<div class="wp-form-row">
<input
id="wpEmail"
name="email"
type="email"
required
placeholder="you@company.com"
autocomplete="email"
/>
<button type="submit" class="btn on-dark wp-form-submit">
Send PDF <span class="arrow" aria-hidden="true">→</span>
</button>
</div>
<p class="wp-form-note" id="wpFormNote">36-page PDF, delivered to your inbox. We never share emails.</p>
</form>
<div class="wp-success hidden" id="wpSuccess">
<h4>Check your inbox.</h4>
<p>The white paper is on its way. If it doesn't arrive in a few minutes, check spam or <a href="mailto:hello@zeroauth.dev" style="color:var(--ink-inverse); text-decoration: underline;">email us</a>.</p>
</div>
</div>
</div>
Expand Down Expand Up @@ -1449,10 +1505,23 @@ <h4>Company</h4>

<div class="footer-bottom">
<span>© 2026 Yushu Excellence Technologies Pvt. Ltd. · All rights reserved.</span>
<a href="/api/health" class="status" target="_blank" rel="noopener">
<span class="dot" aria-hidden="true"></span>
All systems operational
</a>
<div class="footer-socials" aria-label="Social links">
<a href="https://www.linkedin.com/company/zeroauth-dev" target="_blank" rel="noopener" aria-label="ZeroAuth on LinkedIn">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M20.45 20.45h-3.55v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.95v5.66H9.36V9h3.41v1.56h.05c.47-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45zM5.34 7.43a2.06 2.06 0 1 1 0-4.12 2.06 2.06 0 0 1 0 4.12zM7.12 20.45H3.56V9h3.56zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z"/>
</svg>
</a>
<a href="https://github.com/zeroauth-dev" target="_blank" rel="noopener" aria-label="ZeroAuth on GitHub">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 .5C5.65.5.5 5.65.5 12c0 5.08 3.29 9.39 7.86 10.91.58.11.79-.25.79-.55v-2.13c-3.2.7-3.87-1.37-3.87-1.37-.52-1.33-1.28-1.68-1.28-1.68-1.05-.71.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.76 2.7 1.25 3.36.96.1-.75.4-1.25.73-1.54-2.55-.29-5.24-1.28-5.24-5.69 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.47.11-3.06 0 0 .97-.31 3.18 1.18a11.04 11.04 0 0 1 5.79 0c2.21-1.49 3.18-1.18 3.18-1.18.63 1.59.23 2.77.11 3.06.74.81 1.18 1.84 1.18 3.1 0 4.42-2.69 5.39-5.25 5.68.41.36.78 1.06.78 2.13v3.16c0 .31.21.67.8.55 4.56-1.52 7.85-5.83 7.85-10.91C23.5 5.65 18.35.5 12 .5z"/>
</svg>
</a>
<a href="https://x.com/zeroauth-dev" target="_blank" rel="noopener" aria-label="ZeroAuth on X">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/>
</svg>
</a>
</div>
</div>
</div>
</footer>
Expand Down Expand Up @@ -1491,6 +1560,42 @@ <h4>Company</h4>
}
})();

// Whitepaper form — POST email to /api/leads/whitepaper, backend emails the PDF.
(function () {
var form = document.getElementById('wpForm');
if (!form) return;
form.addEventListener('submit', async function (e) {
e.preventDefault();
var emailInput = document.getElementById('wpEmail');
var note = document.getElementById('wpFormNote');
var btn = form.querySelector('button[type="submit"]');
var email = (emailInput.value || '').trim();
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
note.classList.add('error');
note.textContent = 'Please enter a valid email address.';
emailInput.focus();
return;
}
note.classList.remove('error');
note.textContent = 'Sending…';
btn.disabled = true;
try {
var res = await fetch('/api/leads/whitepaper', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email })
});
if (!res.ok) throw new Error('http ' + res.status);
form.classList.add('hidden');
document.getElementById('wpSuccess').classList.remove('hidden');
Comment on lines +1589 to +1590
} catch (err) {
btn.disabled = false;
note.classList.add('error');
note.textContent = 'Could not send. Try again in a moment.';
}
});
})();

// Pilot form submission
(function () {
var form = document.getElementById('pilotForm');
Expand Down
60 changes: 58 additions & 2 deletions src/routes/leads.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
import { Router, Request, Response } from 'express';
import fs from 'fs';
import path from 'path';
import { authenticateAdmin } from '../middleware/auth';
import { logger } from '../services/logger';
import { getPoolOrNull } from '../services/db';
import { sendMail } from '../services/email';
import { whitepaperEmail } from '../services/email-templates';

const router = Router();

/**
* Resolve the whitepaper PDF path across environments. The Dockerfile builds
* the docs site into website/build/, so production reads from there. In dev
* we fall back to the source PDFs in website/static or docs/. Resolved once
* at first use; null means no PDF is shipped with this build.
*/
let whitepaperPathCache: string | null | undefined;
function resolveWhitepaperPath(): string | null {
if (whitepaperPathCache !== undefined) return whitepaperPathCache;
const candidates = [
path.resolve(__dirname, '..', '..', 'website', 'build', 'whitepaper.pdf'),
path.resolve(__dirname, '..', '..', 'website', 'static', 'whitepaper.pdf'),
path.resolve(__dirname, '..', '..', 'docs', 'whitepaper.pdf'),
];
for (const p of candidates) {
if (fs.existsSync(p)) {
whitepaperPathCache = p;
return p;
}
}
whitepaperPathCache = null;
return null;
}

function isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Expand Down Expand Up @@ -83,11 +111,39 @@ router.post('/whitepaper', async (req: Request, res: Response) => {
}

logger.info('Whitepaper lead submitted', { email: trimmedEmail });

// Best-effort mail delivery — non-blocking for the response. If SMTP is
// unconfigured (dev) the call no-ops; the downloadUrl in the response is
// the fallback so the user can still read the paper.
const pdfPath = resolveWhitepaperPath();
if (pdfPath) {
const { subject, html, text } = whitepaperEmail();
void sendMail({
to: trimmedEmail,
subject,
html,
text,
attachments: [
{
filename: 'ZeroAuth_Whitepaper.pdf',
path: pdfPath,
contentType: 'application/pdf',
},
],
}).then((result) => {
Comment on lines +121 to +133
if (!result.ok && !result.skipped) {
logger.warn('Whitepaper email send failed', { error: result.error });
}
});
} else {
logger.warn('Whitepaper PDF not found on disk — email will not include attachment');
}

res.status(201).json({
success: true,
message: 'Whitepaper access granted.',
message: 'Whitepaper sent. Check your inbox in a minute.',
downloadUrl: '/docs/whitepaper.pdf',
filename: 'Pramaan_Whitepaper.pdf',
filename: 'ZeroAuth_Whitepaper.pdf',
});
});

Expand Down
Loading
Loading