Files
catalog/ts_web/elements/idp-landing-page.ts
T

669 lines
24 KiB
TypeScript

import { DeesElement, html, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
import './idp-badge.js';
import './idp-button.js';
import './idp-icon.js';
import './idp-landing-hero.js';
declare global {
interface HTMLElementTagNameMap {
'idp-landing-page': IdpLandingPage;
}
}
@customElement('idp-landing-page')
export class IdpLandingPage extends DeesElement {
public static demo = () => html`<idp-landing-page></idp-landing-page>`;
public static demoGroups = ['idp.global v3 full pages'];
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
--idp-bg: #0a0a0a;
--idp-bg-2: #111111;
--idp-card: #121212;
--idp-card-2: #161616;
--idp-fg: #fafafa;
--idp-fg-2: #d4d4d8;
--idp-fg-3: hsl(0 0% 70%);
--idp-muted-fg: hsl(0 0% 55%);
--idp-border: #262626;
--idp-border-soft: #1c1c1c;
--idp-border-strong: #333333;
--idp-accent: #3b82f6;
--idp-accent-hover: #60a5fa;
background: var(--idp-bg);
color: var(--idp-fg);
}
.page {
min-height: 100vh;
background: var(--idp-bg);
}
nav {
position: sticky;
top: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
height: 56px;
max-width: 1240px;
margin: 0 auto;
padding: 0 32px;
border-bottom: 1px solid var(--idp-border-soft);
background: rgba(10,10,10,0.86);
backdrop-filter: blur(14px) saturate(140%);
}
.nav-shell {
position: sticky;
top: 0;
z-index: 20;
border-bottom: 1px solid var(--idp-border-soft);
background: rgba(10,10,10,0.86);
}
.logo {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--idp-display);
font-size: 16px;
font-weight: 700;
letter-spacing: -0.015em;
}
.logo-dot {
width: 6px;
height: 6px;
border-radius: 999px;
background: var(--idp-accent);
box-shadow: 0 0 12px var(--idp-accent);
}
.links, .actions {
display: flex;
align-items: center;
gap: 6px;
}
.links a {
padding: 6px 12px;
border-radius: 5px;
color: var(--idp-fg-3);
font-size: 13px;
text-decoration: none;
}
.links a:hover {
background: rgba(255,255,255,0.04);
color: var(--idp-fg);
}
.status {
display: inline-flex;
align-items: center;
gap: 6px;
margin-right: 8px;
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
}
.live-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--idp-ok);
box-shadow: 0 0 8px var(--idp-ok);
}
.wrap {
max-width: 1240px;
margin: 0 auto;
padding: 0 32px;
}
.proof, .section, .manifesto, .cta, footer {
border-bottom: 1px solid var(--idp-border-soft);
}
.proof {
padding: 56px 0;
}
.proof-label {
margin-bottom: 28px;
color: var(--idp-muted-fg);
text-align: center;
font-family: var(--idp-mono);
font-size: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.proof-row {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 24px;
place-items: center;
opacity: 0.72;
}
.proof-name {
color: var(--idp-fg-3);
font-family: var(--idp-display);
font-size: 18px;
font-weight: 700;
letter-spacing: -0.02em;
}
.section {
padding: 120px 0;
}
.section.alt {
background: var(--idp-bg-2);
}
.section-head {
max-width: 760px;
margin: 0 auto 64px;
text-align: center;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.eyebrow::before, .eyebrow::after {
content: '';
width: 24px;
height: 1px;
background: var(--idp-border-strong);
}
h2, h3, q {
margin: 0;
font-family: var(--idp-display);
letter-spacing: -0.03em;
}
h2 {
font-size: clamp(36px, 4.5vw, 56px);
line-height: 1.05;
}
em {
color: var(--idp-accent-hover);
font-family: var(--idp-serif);
font-style: italic;
font-weight: 400;
}
.lede {
max-width: 640px;
margin: 20px auto 0;
color: var(--idp-fg-3);
font-size: 17px;
line-height: 1.55;
}
.bento {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 16px;
}
.tile, .tier, .chain-panel, .terminal {
border: 1px solid var(--idp-border-soft);
border-radius: 12px;
background: var(--idp-bg-2);
}
.tile {
padding: 28px;
}
.tile.col-2 { grid-column: span 2; }
.tile.col-3 { grid-column: span 3; }
.tile.tall { grid-row: span 2; }
.tile-tag {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 14px;
color: var(--idp-accent-hover);
font-family: var(--idp-mono);
font-size: 10.5px;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.tile-tag::before {
content: '';
width: 6px;
height: 6px;
border-radius: 999px;
background: var(--idp-accent);
}
.tile h3 {
margin-bottom: 10px;
font-size: 24px;
line-height: 1.15;
}
.tile p, .tier li, .chain-step p {
color: var(--idp-fg-3);
font-size: 14px;
line-height: 1.55;
}
.approval-stack {
display: grid;
gap: 6px;
margin-top: 22px;
}
.approval-row {
display: grid;
grid-template-columns: 28px 1fr auto;
gap: 12px;
align-items: center;
padding: 10px 12px;
border: 1px solid var(--idp-border-soft);
border-left: 2px solid var(--idp-accent);
border-radius: 6px;
background: var(--idp-bg);
}
.avatar {
width: 28px;
height: 28px;
display: grid;
place-items: center;
border: 1px solid var(--idp-border);
border-radius: 50%;
background: var(--idp-card-2);
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 700;
}
.approval-row strong {
display: block;
color: var(--idp-fg);
font-size: 12.5px;
font-weight: 500;
}
.approval-row span.meta {
color: color-mix(in srgb, var(--idp-muted-fg), transparent 30%);
font-family: var(--idp-mono);
font-size: 10.5px;
}
.identity-card {
position: relative;
overflow: hidden;
margin-top: 22px;
border: 1px solid var(--idp-border);
border-radius: 10px;
padding: 20px;
background: linear-gradient(140deg, #1a1a1a 0%, #0a0a0a 100%);
}
.identity-card::after {
content: '';
position: absolute;
top: -100px;
right: -80px;
width: 240px;
height: 240px;
border-radius: 50%;
background: radial-gradient(circle, rgba(0,105,242,0.4), transparent 65%);
}
.identity-card > * {
position: relative;
z-index: 1;
}
.chip {
width: 32px;
height: 24px;
margin: 18px 0 14px;
border-radius: 3px;
background: linear-gradient(135deg, #93bbfd 0%, #0050b9 80%);
}
.mono {
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 10.5px;
}
.metric {
margin-top: 8px;
background: linear-gradient(180deg, var(--idp-fg) 0%, var(--idp-muted-fg) 110%);
background-clip: text;
color: transparent;
font-family: var(--idp-display);
font-size: 64px;
font-weight: 700;
letter-spacing: -0.04em;
line-height: 1;
}
.metric span {
font-size: 24px;
}
.devices-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-top: 22px;
}
.dev-cell {
padding: 14px 10px;
border: 1px solid var(--idp-border-soft);
border-radius: 6px;
background: var(--idp-bg);
text-align: center;
}
.dev-icon {
width: 32px;
height: 32px;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 8px;
border: 1px solid rgba(96,165,250,0.35);
border-radius: 9px;
color: var(--idp-accent-hover);
}
.dev-name {
color: var(--idp-fg);
font-size: 12px;
font-weight: 500;
}
.dev-sub {
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 10px;
}
.chain-grid, .dev-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
}
.chain-steps {
display: grid;
gap: 28px;
}
.chain-step {
display: grid;
grid-template-columns: 70px 1fr;
gap: 16px;
padding-bottom: 24px;
border-bottom: 1px solid var(--idp-border-soft);
}
.chain-step > div {
color: var(--idp-accent-hover);
font-family: var(--idp-mono);
font-size: 11px;
font-weight: 600;
letter-spacing: 0.08em;
}
.chain-panel {
overflow: hidden;
background: var(--idp-bg);
}
.chain-head {
display: flex;
justify-content: space-between;
padding: 14px 18px;
border-bottom: 1px solid var(--idp-border-soft);
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.chain-block {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
margin: 8px 14px;
padding: 14px 16px;
border: 1px solid var(--idp-border-soft);
border-radius: 8px;
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
}
.chain-block.idp {
border-left: 2px solid var(--idp-accent);
background: rgba(0,80,185,0.08);
}
.tiers {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.tier {
position: relative;
display: flex;
flex-direction: column;
padding: 28px;
}
.tier.featured {
border-color: var(--idp-accent);
background: linear-gradient(180deg, rgba(59,130,246,0.06) 0%, var(--idp-bg-2) 40%);
}
.tier-name {
margin-bottom: 10px;
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.price {
margin: 12px 0 20px;
font-family: var(--idp-display);
font-size: 40px;
font-weight: 700;
}
.tier ul {
flex: 1;
margin: 0 0 24px;
padding-left: 20px;
}
.dev-text p {
color: var(--idp-fg-3);
}
.tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin: 18px 0 22px;
}
.tags span {
padding: 4px 10px;
border: 1px solid var(--idp-border);
border-radius: 999px;
background: var(--idp-bg-2);
color: var(--idp-fg-3);
font-family: var(--idp-mono);
font-size: 10.5px;
}
.terminal {
overflow: hidden;
background: var(--idp-bg);
}
.term-bar {
display: flex;
gap: 6px;
padding: 10px 14px;
border-bottom: 1px solid var(--idp-border-soft);
}
.tdot {
width: 11px;
height: 11px;
border-radius: 50%;
}
.red { background: #ff5f57; }
.yellow { background: #ffbd2e; }
.green { background: #28c840; }
pre {
min-height: 300px;
margin: 0;
padding: 22px 24px;
color: var(--idp-fg-3);
font-family: var(--idp-mono);
font-size: 13px;
line-height: 1.85;
}
.manifesto, .cta {
padding: 120px 0;
text-align: center;
}
q {
display: block;
max-width: 980px;
margin: 0 auto;
font-family: var(--idp-serif);
font-size: clamp(32px, 4vw, 48px);
font-style: italic;
line-height: 1.2;
quotes: none;
}
q::before, q::after {
content: none;
}
.cta {
position: relative;
overflow: hidden;
}
.cta::before {
content: '';
position: absolute;
left: 50%;
top: 50%;
width: 800px;
height: 600px;
transform: translate(-50%, -50%);
background: radial-gradient(ellipse, rgba(59,130,246,0.15) 0%, transparent 60%);
}
.cta .wrap {
position: relative;
}
.cta p {
max-width: 560px;
margin: 24px auto 32px;
color: var(--idp-fg-3);
}
footer {
padding: 64px 0 28px;
border-bottom: 0;
}
.footer-cols {
display: grid;
grid-template-columns: 2fr 1fr 1fr 1fr;
gap: 48px;
margin-bottom: 48px;
}
.footer-brand p, .footer-col a, .footer-bottom {
color: var(--idp-muted-fg);
font-size: 13px;
}
.footer-col {
display: grid;
gap: 9px;
}
.footer-col h4 {
margin: 0;
color: var(--idp-fg-3);
font-family: var(--idp-mono);
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.footer-bottom {
display: flex;
justify-content: space-between;
padding-top: 22px;
border-top: 1px solid var(--idp-border-soft);
font-family: var(--idp-mono);
font-size: 11px;
}
@media (max-width: 1100px) {
.links { display: none; }
.bento { grid-template-columns: repeat(2, 1fr); }
.tile.col-2, .tile.col-3 { grid-column: span 2; }
.chain-grid, .dev-grid { grid-template-columns: 1fr; }
.tiers { grid-template-columns: 1fr; max-width: 520px; margin: 0 auto; }
.footer-cols { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 720px) {
nav, .wrap { padding-left: 20px; padding-right: 20px; }
.status, .actions .ghost { display: none; }
.proof-row { grid-template-columns: repeat(2, 1fr); }
.bento { grid-template-columns: 1fr; }
.tile.col-2, .tile.col-3 { grid-column: span 1; }
.section, .manifesto, .cta { padding: 80px 0; }
.footer-cols { grid-template-columns: 1fr; }
}
`,
];
private renderNav() {
return html`
<div class="nav-shell">
<nav>
<div class="logo">idp<span class="logo-dot"></span>global</div>
<div class="links"><a href="#product">Product</a><a href="#features">Features</a><a href="#chain">On-chain</a><a href="#pricing">Pricing</a><a href="#developers">Developers</a></div>
<div class="actions"><span class="status"><span class="live-dot"></span>All systems normal</span><idp-button variant="ghost" size="sm">Sign in</idp-button><idp-button variant="accent" size="sm">Claim identity</idp-button></div>
</nav>
</div>
`;
}
private renderFeatures() {
return html`
<section class="section" id="features">
<div class="wrap">
<div class="section-head"><div class="eyebrow">Capabilities</div><h2>Native on every screen <em>you already carry.</em></h2><p class="lede">Approvals on iPhone. Tap-to-auth via NFC. Lock-screen actions on Apple Watch. The same identity, one tap away on any device.</p></div>
<div class="bento">
<div class="tile col-3 tall"><div class="tile-tag">Push approvals</div><h3>Approve or deny <em>in one tap.</em></h3><p>Every login, OAuth grant, and sensitive action triggers a real-time approval.</p><div class="approval-stack">${['GitHub OAuth|repo:read - 2 min ago|approved|ok', 'CLI login - MacBook Pro|Berlin - just now|pending|accent', 'Unknown device|Lagos - 1 hr ago|denied|error', 'NFC tap - door 4F|HQ - 12 min ago|approved|ok'].map((rowArg) => { const row = rowArg.split('|'); return html`<div class="approval-row"><div class="avatar">${row[0].slice(0,2).toUpperCase()}</div><div><strong>${row[0]}</strong><span class="meta">${row[1]}</span></div><idp-badge variant=${row[3] as any}>${row[2]}</idp-badge></div>`; })}</div></div>
<div class="tile col-3"><div class="tile-tag">NFC tap-to-auth</div><h3>Tap to <em>authenticate.</em></h3><p>Hold your phone to any compatible reader. Identity token exchanges in under a second.</p><div class="identity-card"><h3>Alex Mercer</h3><div class="mono">@alexmercer - Personal</div><div class="chip"></div><div class="mono">did:idp:0x4a3f...c819</div></div></div>
<div class="tile col-3"><div class="tile-tag">Four platforms</div><h3>iPhone, Watch, iPad, Mac.</h3><p>Every device you carry is a trusted authenticator.</p><div class="devices-row">${[
['iPhone', 'phone'],
['Watch', 'smartphone-nfc'],
['iPad', 'device'],
['Mac', 'monitor'],
].map((deviceArg) => html`<div class="dev-cell"><div class="dev-icon"><idp-icon name=${deviceArg[1] as any} size="18"></idp-icon></div><div class="dev-name">${deviceArg[0]}</div><div class="dev-sub">trusted</div></div>`)}</div><div class="mono" style="margin-top:14px">One approval, anywhere - synchronized end-to-end.</div></div>
<div class="tile col-2"><div class="tile-tag">Average approval</div><h3>Sub-second auth.</h3><p>Push delivery, biometric prompt, and signed response under a second.</p><div class="metric">0.8<span>sec</span></div></div>
<div class="tile col-2"><div class="tile-tag">Audit-grade</div><h3>Every action, <em>on the record.</em></h3><p>Tamper-evident audit trail per identity and organization.</p></div>
<div class="tile col-2"><div class="tile-tag">Recovery</div><h3>Lose a phone? Not your identity.</h3><p>Multi-device recovery or social-recovery quorum. No vendor lockout.</p></div>
</div>
</div>
</section>
`;
}
private renderChain() {
return html`
<section class="section alt" id="chain"><div class="wrap"><div class="section-head"><div class="eyebrow">Cardano-anchored</div><h2>Your identity outlives <em>any single server.</em></h2><p class="lede">Every identity is anchored to the Cardano mainnet, independently verifiable and recoverable.</p></div><div class="chain-grid"><div class="chain-steps">${[['01 / 03', 'Immutable record', 'Your identity hash is written to Cardano at creation and on every key rotation.'], ['02 / 03', 'Synced on every change', 'Profile updates, device additions, and revocations are anchored to the chain.'], ['03 / 03', 'Independently verifiable', 'Any compatible resolver can verify your identity directly against the public ledger.']].map((stepArg) => html`<div class="chain-step"><div>${stepArg[0]}</div><section><h3>${stepArg[1]}</h3><p>${stepArg[2]}</p></section></div>`)}</div><div class="chain-panel"><div class="chain-head"><span>Cardano mainnet</span><idp-badge variant="accent">live</idp-badge></div>${['#9 841 220', '#9 841 221', '#9 841 222', '#9 841 223'].map((blockArg, indexArg) => html`<div class="chain-block ${indexArg === 1 || indexArg === 2 ? 'idp' : ''}"><span>${blockArg}</span><span>${indexArg === 1 ? 'did:idp:0x4a3f...c819' : indexArg === 2 ? 'did:idp:0x9b12...f034' : 'confirmed block'}</span><strong>${indexArg === 1 || indexArg === 2 ? 'idp.global' : 'confirmed'}</strong></div>`)}</div></div></div></section>
`;
}
private renderPricing() {
const tiers = [
['Personal', 'For one person.', '$0', ['One portable identity', 'Push approval on devices', 'NFC tap-to-authenticate', 'Anchored on Cardano'], 'Claim your identity'],
['Family & Org', 'For teams under 1,000.', '$0', ['Multi-member organization', 'Role-based access control', 'Shared OAuth client registry', 'Full audit trail'], 'Start an organization'],
['Enterprise', 'Above $1M ARR.', 'Fair', ['Self-hosted and air-gap deployable', 'Compliance and audit support', 'Global admin across orgs', 'Priority SLA'], 'Talk to us'],
];
return html`<section class="section" id="pricing"><div class="wrap"><div class="section-head"><div class="eyebrow">Pricing</div><h2>The same identity, <em>at every scale.</em></h2><p class="lede">Free for the first thousand users. Fair contribution above that. No hard paywalls.</p></div><div class="tiers">${tiers.map((tierArg, indexArg) => html`<div class="tier ${indexArg === 1 ? 'featured' : ''}"><div class="tier-name">${tierArg[0]}</div><h3>${tierArg[1]}</h3><div class="price">${tierArg[2]}</div><ul>${(tierArg[3] as string[]).map((itemArg) => html`<li>${itemArg}</li>`)}</ul><idp-button variant=${indexArg === 1 ? 'accent' : 'ghost'}>${tierArg[4]}</idp-button></div>`)}</div></div></section>`;
}
private renderDevelopers() {
return html`
<section class="section alt" id="developers"><div class="wrap dev-grid"><div class="dev-text"><div class="eyebrow">For developers</div><h2>No black boxes <em>in your identity stack.</em></h2><p>idp.global is fully open source and MIT licensed. Read the cryptography. Verify the Cardano sync. Run it on your own metal.</p><div class="tags">${['MIT licensed', 'OAuth 2 / OIDC', 'Self-hostable', 'Air-gappable', 'Cardano native', 'SOC 2'].map((tagArg) => html`<span>${tagArg}</span>`)}</div><idp-button variant="accent">View source</idp-button></div><div class="terminal"><div class="term-bar"><span class="tdot red"></span><span class="tdot yellow"></span><span class="tdot green"></span></div><pre><code>$ idp identity create
OK Identity created - did:idp:0x4a3f...c819
OK Confirmed on-chain - permanent
$ idp login github.com
OK Push sent - iPhone 15 Pro
OK Approved - Watch - 0.8s</code></pre></div></div></section>
`;
}
public render(): TemplateResult {
return html`
<div class="page">
${this.renderNav()}
<idp-landing-hero></idp-landing-hero>
<section class="proof"><div class="wrap"><div class="proof-label">Built for identity at every scale</div><div class="proof-row">${['Open Source', 'Self-hostable', 'Cardano anchored', 'OIDC ready', 'Passkey first', 'Free for everyone'].map((nameArg) => html`<div class="proof-name">${nameArg}</div>`)}</div></div></section>
${this.renderFeatures()}${this.renderChain()}${this.renderPricing()}${this.renderDevelopers()}
<section class="manifesto"><div class="wrap"><div class="eyebrow">Why we built this</div><q>Identity should not be a product the user is sold.<br/>It should be a permanent <em>fact</em>, owned by the person it describes.</q></div></section>
<section class="cta"><div class="wrap"><h2>Claim your identity.<br/><em>Free, forever.</em></h2><p>Sixty seconds to claim, anchored to Cardano on submission. No credit card. No vendor lock-in.</p><idp-button variant="accent" size="lg">Claim your identity</idp-button></div></section>
<footer><div class="wrap"><div class="footer-cols"><div class="footer-brand"><div class="logo">idp<span class="logo-dot"></span>global</div><p>An open identity provider for everyone. Anchored on Cardano. Built in the open. Yours forever.</p></div><div class="footer-col"><h4>Product</h4><a>For individuals</a><a>For organizations</a><a>Cardano sync</a></div><div class="footer-col"><h4>Developers</h4><a>Documentation</a><a>Self-hosting</a><a>SDKs</a></div><div class="footer-col"><h4>Company</h4><a>Manifesto</a><a>Security</a><a>Privacy</a></div></div><div class="footer-bottom"><span>© 2026 idp.global - MIT - Anchored to Cardano</span><span>Source - Community</span></div></div></footer>
</div>
`;
}
}