feat(catalog): add initial idp.global component catalog with primitives, composed views, and full-page showcases

This commit is contained in:
2026-05-03 10:11:06 +00:00
commit cd5eac437c
35 changed files with 11775 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
.nogit/
# installs
node_modules/
# builds
dist/
dist_*/
# caches
.cache/
.rpt2_cache
+43
View File
@@ -0,0 +1,43 @@
{
"@git.zone/cli": {
"projectType": "wcc",
"module": {
"githost": "code.foss.global",
"gitscope": "idp.global",
"gitrepo": "catalog",
"description": "Web component catalog for idp.global, based on the v2 product design language.",
"npmPackagename": "@idp.global/catalog",
"license": "MIT",
"projectDomain": "idp.global",
"keywords": [
"idp.global",
"web components",
"identity",
"authentication",
"design system"
]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"@git.zone/tsbundle": {
"bundles": [
{
"from": "./ts_web/index.ts",
"to": "./dist_bundle/bundle.js",
"outputMode": "bundle",
"bundler": "esbuild",
"production": true
}
]
},
"@git.zone/tswatch": {
"preset": "element"
},
"@ship.zone/szci": {}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

+13
View File
@@ -0,0 +1,13 @@
# Changelog
## 2026-05-03 - 1.1.0 - feat(catalog)
add initial idp.global component catalog with primitives, composed views, and full-page showcases
- Adds a new web component catalog package with reusable UI primitives such as buttons, badges, cards, inputs, toggles, and icons.
- Introduces composed identity surfaces including approval cards, inbox previews, mobile frames, dashboard windows, admin shell views, and landing hero sections.
- Includes full-page catalog demos and workspace setup for browsing landing, admin, and mobile showcase experiences.
- Configures package metadata, build tooling, documentation files, and bundled design assets for the initial 1.0.0 release.
## 1.0.0
- Initial idp.global v2 element catalog.
+29
View File
@@ -0,0 +1,29 @@
<!--gitzone element-->
<!-- made by Task Venture Capital GmbH -->
<!-- checkout https://maintainedby.lossless.com for awesome OpenSource projects -->
<html lang="en">
<head>
<!--Lets set some basic meta tags-->
<meta
name="viewport"
content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height"
/>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!--Lets load standard fonts-->
<link rel="preconnect" href="https://assetbroker.lossless.one/" crossorigin>
<link rel="stylesheet" href="https://assetbroker.lossless.one/fonts/fonts.css">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%233b82f6' d='M12 2l8 3v7c0 6-8 10-8 10s-8-4-8-10V5l8-3z'/%3E%3C/svg%3E">
<style>
body {
margin: 0px;
background: #222222;
}
</style>
<script type="module" src="/bundle.js"></script>
</head>
<body>
</body>
</html>
+39
View File
@@ -0,0 +1,39 @@
// dees tools
import * as deesWccTools from '@design.estate/dees-wcctools';
import * as deesDomTools from '@design.estate/dees-domtools';
// elements and pages
import * as elements from '../ts_web/elements/index.js';
import * as pages from '../ts_web/pages/index.js';
const fullPageElementNames = new Set([
'IdpAdminShell',
'IdpLandingPage',
'IdpMobileShowcase',
]);
deesWccTools.setupWccTools({
sections: [
{
name: 'Full Pages',
type: 'pages',
items: pages,
icon: 'web',
},
{
name: 'Composed Views',
type: 'elements',
items: elements,
icon: 'dashboard',
filter: (nameArg) => fullPageElementNames.has(nameArg) || ['IdpDashboardWindow', 'IdpLandingHero', 'IdpInboxPreview'].includes(nameArg),
},
{
name: 'Primitives',
type: 'elements',
items: elements,
icon: 'category',
filter: (nameArg) => nameArg.startsWith('Idp') && !fullPageElementNames.has(nameArg) && !['IdpDashboardWindow', 'IdpLandingHero', 'IdpInboxPreview'].includes(nameArg),
},
],
});
deesDomTools.elementBasic.setup();
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Task Venture Capital GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+65
View File
@@ -0,0 +1,65 @@
{
"name": "@idp.global/catalog",
"version": "1.0.0",
"private": false,
"description": "Web component catalog for idp.global, based on the v3 product design language.",
"exports": {
".": "./dist_ts_web/index.js"
},
"main": "dist_ts_web/index.js",
"typings": "dist_ts_web/index.d.ts",
"type": "module",
"scripts": {
"test": "pnpm run build",
"build": "tsbuild tsfolders --allowimplicitany && tsbundle",
"watch": "tswatch",
"buildDocs": "tsdoc"
},
"author": "Task Venture Capital GmbH",
"license": "MIT",
"dependencies": {
"@design.estate/dees-catalog": "^3.81.0",
"@design.estate/dees-domtools": "^2.5.4",
"@design.estate/dees-element": "^2.2.4",
"@design.estate/dees-wcctools": "^3.9.0",
"lucide": "^1.14.0"
},
"devDependencies": {
"@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsbundle": "^2.10.0",
"@git.zone/tswatch": "^3.3.2",
"@push.rocks/projectinfo": "^5.1.0",
"@types/node": "^25.6.0"
},
"files": [
"ts_web/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"html/**/*",
"license",
"readme.md",
"changelog.md"
],
"repository": {
"type": "git",
"url": "git+ssh://git@code.foss.global:29419/idp.global/catalog.git"
},
"bugs": {
"url": "https://code.foss.global/idp.global/catalog/issues"
},
"homepage": "https://code.foss.global/idp.global/catalog#readme",
"browserslist": [
"last 1 Chrome versions"
],
"keywords": [
"idp.global",
"catalog",
"web components",
"identity",
"authentication",
"design system"
],
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b8712034030524f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
}
+6665
View File
File diff suppressed because it is too large Load Diff
+48
View File
@@ -0,0 +1,48 @@
# @idp.global/catalog
Web component catalog for idp.global, based on the v2 product design language.
The catalog converts the `idp.global v2.zip` design studies into reusable custom elements for landing pages, approval surfaces, account/admin shells, and mobile-style identity flows.
## Install
```bash
pnpm add @idp.global/catalog
```
## Usage
```ts
import '@idp.global/catalog';
```
```html
<idp-landing-hero></idp-landing-hero>
<idp-approval-card app-name="GitHub" request-text="Sign in to github.com"></idp-approval-card>
```
## Elements
- `idp-button` for v2 button variants.
- `idp-badge` for small status labels.
- `idp-card` for bordered shadcn-style content containers.
- `idp-input` for text/password/email inputs.
- `idp-toggle` for compact boolean controls.
- `idp-icon` for bundled monochrome identity icons.
- `idp-approval-card` for approve/deny request cards.
- `idp-inbox-preview` for mobile approval inbox previews.
- `idp-mobile-frame` for iOS-style product screenshots.
- `idp-admin-shell` for account/org admin layout previews.
- `idp-landing-hero` for the public landing hero section.
## Scope
This package is component-only. It does not perform authentication, store sessions, or call the idp.global backend.
## Development
```bash
pnpm install
pnpm run build
pnpm test
```
+8
View File
@@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@idp.global/catalog',
version: '1.1.0',
description: 'Web component catalog for idp.global, based on the v3 product design language.'
}
File diff suppressed because it is too large Load Diff
+182
View File
@@ -0,0 +1,182 @@
import { DeesElement, html, property, 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';
declare global {
interface HTMLElementTagNameMap {
'idp-approval-card': IdpApprovalCard;
}
}
@customElement('idp-approval-card')
export class IdpApprovalCard extends DeesElement {
public static demo = () => html`
<idp-approval-card
app-name="GitHub"
app-initials="GH"
app-color="#24292F"
request-text="Sign in to github.com"
location="Berlin · DE"
device="Safari · MacBook Pro"
risk="trusted"
time-label="now"
></idp-approval-card>
`;
public static demoGroups = ['idp.global v3 approval surfaces'];
@property({ type: String, attribute: 'app-name' })
public accessor appName = 'GitHub';
@property({ type: String, attribute: 'app-initials' })
public accessor appInitials = 'GH';
@property({ type: String, attribute: 'app-color' })
public accessor appColor = '#24292F';
@property({ type: String, attribute: 'request-text' })
public accessor requestText = 'Sign in to github.com';
@property({ type: String })
public accessor location = 'Berlin · DE';
@property({ type: String })
public accessor device = 'Safari · MacBook Pro';
@property({ type: String })
public accessor risk: 'trusted' | 'warning' | 'low' = 'trusted';
@property({ type: String, attribute: 'time-label' })
public accessor timeLabel = 'now';
@property({ type: Boolean, reflect: true })
public accessor primary = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
.card {
background: var(--idp-card);
border: 1px solid var(--idp-border);
border-radius: 12px;
padding: 14px;
color: var(--idp-fg);
}
:host([primary]) .card {
border-color: var(--idp-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--idp-accent), transparent 92%);
}
.top {
display: flex;
gap: 12px;
align-items: flex-start;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 8px;
background: var(--app-color);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 750;
letter-spacing: -0.03em;
flex: 0 0 auto;
}
.body {
flex: 1;
min-width: 0;
}
.line {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.app {
font-size: 14px;
font-weight: 650;
letter-spacing: -0.02em;
}
.time, .sub, .meta {
color: var(--idp-muted-fg);
}
.time {
font-size: 12px;
}
.sub {
margin-top: 1px;
font-size: 13px;
}
.meta {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
margin-top: 9px;
font-size: 12px;
}
.meta-item {
display: inline-flex;
align-items: center;
gap: 4px;
}
.actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
idp-button:first-child {
flex: 1;
}
idp-button:last-child {
flex: 2;
}
`,
];
private dispatchAction(actionArg: 'approve' | 'deny') {
this.dispatchEvent(new CustomEvent(`idp-${actionArg}`, {
detail: {
appName: this.appName,
requestText: this.requestText,
},
bubbles: true,
composed: true,
}));
}
public render(): TemplateResult {
const badgeVariant = this.risk === 'warning' ? 'warn' : 'ok';
const badgeText = this.risk === 'warning' ? 'new network' : 'trusted';
return html`
<article class="card" style="--app-color: ${this.appColor}">
<div class="top">
<div class="avatar">${this.appInitials}</div>
<div class="body">
<div class="line">
<div class="app">${this.appName}</div>
<div class="time">${this.timeLabel}</div>
</div>
<div class="sub">${this.requestText}</div>
<div class="meta">
<span class="meta-item"><idp-icon name="location" size="12"></idp-icon>${this.location}</span>
<span class="meta-item"><idp-icon name="laptop" size="12"></idp-icon>${this.device}</span>
<idp-badge variant=${badgeVariant as any}>${badgeText}</idp-badge>
</div>
</div>
</div>
<div class="actions">
<idp-button variant="outline" @click=${() => this.dispatchAction('deny')}>Deny</idp-button>
<idp-button variant="accent" icon="check" @click=${() => this.dispatchAction('approve')}>Approve</idp-button>
</div>
</article>
`;
}
}
+84
View File
@@ -0,0 +1,84 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
export type TIdpBadgeVariant = 'default' | 'accent' | 'ok' | 'warn' | 'error' | 'outline';
declare global {
interface HTMLElementTagNameMap {
'idp-badge': IdpBadge;
}
}
@customElement('idp-badge')
export class IdpBadge extends DeesElement {
public static demo = () => html`
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<idp-badge>Default</idp-badge>
<idp-badge variant="accent">Admin</idp-badge>
<idp-badge variant="ok">Trusted</idp-badge>
<idp-badge variant="warn">New network</idp-badge>
<idp-badge variant="error">Denied</idp-badge>
</div>
`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor variant: TIdpBadgeVariant = 'default';
public static styles = [
...idpElementStyles,
css`
:host {
display: inline-flex;
}
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border: 1px solid transparent;
border-radius: 999px;
font-family: var(--idp-mono);
font-size: 11px;
font-weight: 600;
letter-spacing: -0.01em;
line-height: 16px;
}
.default {
background: var(--idp-muted);
color: var(--idp-muted-fg);
}
.accent {
background: var(--idp-accent-soft);
color: var(--idp-accent);
}
.ok {
background: var(--idp-ok-bg);
color: var(--idp-ok);
border-color: var(--idp-ok-border);
}
.warn {
background: var(--idp-warn-bg);
color: var(--idp-warn);
border-color: var(--idp-warn-border);
}
.error {
background: var(--idp-error-bg);
color: var(--idp-error);
border-color: var(--idp-error-border);
}
.accent {
border-color: var(--idp-info-border);
}
.outline {
background: transparent;
color: var(--idp-fg);
border-color: var(--idp-border);
}
`,
];
public render(): TemplateResult {
return html`<span class="badge ${this.variant}"><slot></slot></span>`;
}
}
+145
View File
@@ -0,0 +1,145 @@
import {
DeesElement,
html,
property,
customElement,
css,
type TemplateResult,
} from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
import './idp-icon.js';
export type TIdpButtonVariant = 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'accent';
export type TIdpButtonSize = 'sm' | 'md' | 'lg';
declare global {
interface HTMLElementTagNameMap {
'idp-button': IdpButton;
}
}
@customElement('idp-button')
export class IdpButton extends DeesElement {
public static demo = () => html`
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<idp-button>Default</idp-button>
<idp-button variant="accent">Approve</idp-button>
<idp-button variant="outline">Deny</idp-button>
<idp-button variant="ghost">Ghost</idp-button>
<idp-button variant="destructive">Delete</idp-button>
</div>
`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor variant: TIdpButtonVariant = 'default';
@property({ type: String })
public accessor size: TIdpButtonSize = 'md';
@property({ type: String })
public accessor icon = '';
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: inline-block;
}
:host([disabled]) {
pointer-events: none;
}
button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
border: 1px solid transparent;
border-radius: 6px;
font-family: var(--idp-font);
font-weight: 500;
letter-spacing: -0.01em;
white-space: nowrap;
cursor: pointer;
transition: background 120ms ease, color 120ms ease, border-color 120ms ease, transform 80ms ease;
}
button:active:not(:disabled) {
transform: translateY(1px);
}
button:focus-visible {
outline: 2px solid color-mix(in srgb, var(--idp-accent), transparent 68%);
outline-offset: 2px;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.sm {
height: 32px;
padding: 0 12px;
font-size: 13px;
}
.md {
height: 38px;
padding: 0 14px;
font-size: 14px;
}
.lg {
height: 44px;
padding: 0 18px;
font-size: 15px;
}
.default {
background: var(--idp-primary);
color: var(--idp-primary-fg);
}
.default:hover:not(:disabled) {
opacity: 0.88;
}
.accent {
background: var(--idp-accent);
color: #fff;
box-shadow: 0 4px 14px color-mix(in srgb, var(--idp-accent), transparent 64%);
}
.accent:hover:not(:disabled) {
background: var(--idp-accent-hover);
}
.secondary {
background: var(--idp-muted);
color: var(--idp-fg);
border-color: var(--idp-border);
}
.outline {
background: transparent;
color: var(--idp-fg);
border-color: var(--idp-border);
}
.outline:hover:not(:disabled), .secondary:hover:not(:disabled), .ghost:hover:not(:disabled) {
background: var(--idp-muted);
}
.ghost {
background: transparent;
color: var(--idp-fg);
}
.destructive {
background: var(--idp-destructive);
color: #fff;
}
idp-icon {
flex: 0 0 auto;
}
`,
];
public render(): TemplateResult {
return html`
<button class="${this.variant} ${this.size}" ?disabled=${this.disabled} part="button">
${this.icon ? html`<idp-icon name=${this.icon as any} size="14"></idp-icon>` : html``}
<slot></slot>
</button>
`;
}
}
+76
View File
@@ -0,0 +1,76 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
declare global {
interface HTMLElementTagNameMap {
'idp-card': IdpCard;
}
}
@customElement('idp-card')
export class IdpCard extends DeesElement {
public static demo = () => html`<idp-card headline="Card title" description="Muted supporting text.">Card content</idp-card>`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor headline = '';
@property({ type: String })
public accessor description = '';
@property({ type: Boolean, reflect: true })
public accessor elevated = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
.card {
background: var(--idp-card);
border: 1px solid var(--idp-border);
border-radius: var(--idp-radius-lg);
color: var(--idp-fg);
padding: 20px;
}
:host([elevated]) .card {
box-shadow: 0 8px 24px -10px rgb(0 0 0 / 0.28);
}
.head {
display: flex;
justify-content: space-between;
gap: 16px;
margin-bottom: 16px;
}
.headline {
font-size: 14px;
font-weight: 650;
line-height: 1.35;
}
.description {
color: var(--idp-muted-fg);
font-size: 13px;
line-height: 1.5;
margin-top: 3px;
}
`,
];
public render(): TemplateResult {
return html`
<section class="card" part="card">
${this.headline || this.description ? html`
<div class="head">
<div>
${this.headline ? html`<div class="headline">${this.headline}</div>` : html``}
${this.description ? html`<div class="description">${this.description}</div>` : html``}
</div>
<slot name="action"></slot>
</div>
` : html``}
<slot></slot>
</section>
`;
}
}
+593
View File
@@ -0,0 +1,593 @@
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';
type TDashboardStat = {
label: string;
value: string;
unit?: string;
delta: string;
sub: string;
accent: string;
sparkColor: string;
spark: number[];
live?: boolean;
};
declare global {
interface HTMLElementTagNameMap {
'idp-dashboard-window': IdpDashboardWindow;
}
}
@customElement('idp-dashboard-window')
export class IdpDashboardWindow extends DeesElement {
public static demo = () => html`<idp-dashboard-window dark></idp-dashboard-window>`;
public static demoGroups = ['idp.global v3 composed surfaces'];
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
.dash {
position: relative;
overflow: hidden;
border: 1px solid var(--idp-border);
border-radius: 14px;
background: var(--idp-bg-2);
color: var(--idp-fg);
box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset, 0 40px 80px -20px rgba(0,0,0,0.70), 0 8px 24px rgba(0,0,0,0.35);
}
.chrome, .appbar, .bottom {
display: flex;
align-items: center;
gap: 8px;
border-bottom: 1px solid var(--idp-border-soft);
background: rgba(255,255,255,0.02);
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
}
.chrome {
padding: 11px 14px;
}
.tdot {
width: 11px;
height: 11px;
border-radius: 50%;
}
.red { background: #ff5f57; }
.yellow { background: #ffbd2e; }
.green { background: #28c840; }
.url, .org, .search {
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid var(--idp-border-soft);
border-radius: 5px;
background: var(--idp-bg);
color: var(--idp-fg-3, var(--idp-muted-fg));
}
.url {
margin-left: 12px;
padding: 4px 10px;
}
.status {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 6px;
}
.live-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--idp-ok);
box-shadow: 0 0 8px var(--idp-ok);
animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.35; }
}
.appbar {
justify-content: space-between;
height: 44px;
padding: 0 14px;
background: var(--idp-bg);
}
.appbar-left, .appbar-right {
display: flex;
align-items: center;
gap: 12px;
}
.logo {
display: inline-flex;
align-items: center;
gap: 7px;
color: var(--idp-fg);
font-family: var(--idp-display);
font-size: 13px;
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);
}
.divider {
width: 1px;
height: 18px;
background: var(--idp-border);
}
.org {
padding: 4px 10px 4px 4px;
background: var(--idp-bg-2);
font-size: 12px;
}
.avatar-sm {
width: 20px;
height: 20px;
display: inline-grid;
place-items: center;
border-radius: 4px;
background: var(--idp-accent);
color: #fff;
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 700;
}
.search {
min-width: 240px;
height: 28px;
padding: 0 10px;
background: var(--idp-bg-2);
}
.kbd {
margin-left: auto;
padding: 0 5px;
border: 1px solid var(--idp-border);
border-radius: 3px;
background: var(--idp-bg);
font-size: 10px;
}
.user-avatar {
width: 28px;
height: 28px;
display: inline-grid;
place-items: center;
border: 1px solid rgba(59,130,246,0.4);
border-radius: 50%;
background: rgba(0,80,185,0.25);
color: var(--idp-accent-hover);
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 700;
}
.shell {
display: grid;
grid-template-columns: 200px 1fr;
min-height: 580px;
}
aside {
display: flex;
flex-direction: column;
gap: 1px;
padding: 12px 8px;
border-right: 1px solid var(--idp-border-soft);
background: var(--idp-bg);
}
.side-label {
padding: 12px 10px 6px;
color: color-mix(in srgb, var(--idp-muted-fg), transparent 35%);
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.side-nav {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 10px;
border-radius: 5px;
color: var(--idp-muted-fg);
font-size: 12.5px;
}
.side-nav.active {
background: rgba(0,80,185,0.18);
color: var(--idp-fg);
}
.nav-icon {
width: 18px;
display: inline-flex;
justify-content: center;
color: currentColor;
}
.side-nav.active .nav-icon {
color: var(--idp-accent-hover);
}
main {
min-width: 0;
padding: 22px 24px;
background: var(--idp-bg);
}
.head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
h3 {
margin: 0;
font-family: var(--idp-display);
font-size: 22px;
font-weight: 650;
letter-spacing: -0.02em;
}
.sub {
margin-top: 2px;
color: var(--idp-muted-fg);
font-size: 12.5px;
}
.actions {
display: flex;
gap: 8px;
}
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 16px;
}
.stat, .card {
border: 1px solid var(--idp-border-soft);
background: var(--idp-bg-2);
}
.stat {
position: relative;
min-height: 132px;
display: flex;
flex-direction: column;
gap: 4px;
overflow: hidden;
padding: 18px 20px 14px;
border-radius: 10px;
}
.stat::before {
content: '';
position: absolute;
inset: 0 0 auto;
height: 2px;
background: var(--stat-accent);
}
.stat-label {
color: var(--idp-muted-fg);
font-size: 11.5px;
font-weight: 500;
letter-spacing: 0.02em;
}
.stat-val {
color: var(--idp-fg);
font-family: var(--idp-display);
font-size: 30px;
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: -0.025em;
line-height: 1.1;
}
.stat-val span {
margin-left: 2px;
color: var(--idp-muted-fg);
font-size: 14px;
font-weight: 500;
}
.stat-sub {
color: var(--idp-fg-3);
font-family: var(--idp-mono);
font-size: 11px;
}
.stat-foot {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 8px;
margin-top: auto;
}
.delta {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--idp-ok);
font-family: var(--idp-mono);
font-size: 11px;
font-weight: 600;
}
.sparkline {
width: 84px;
opacity: 0.85;
}
.sparkline svg {
width: 100%;
height: 22px;
display: block;
}
.grid {
display: grid;
grid-template-columns: 1.6fr 1fr;
gap: 16px;
}
.card {
overflow: hidden;
}
.card-head {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid var(--idp-border-soft);
}
.card-title {
font-size: 13px;
font-weight: 600;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 10px 16px;
border-bottom: 1px solid var(--idp-border-soft);
text-align: left;
font-size: 12.5px;
}
th {
color: color-mix(in srgb, var(--idp-muted-fg), transparent 35%);
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.user {
display: flex;
align-items: center;
gap: 8px;
}
.row-avatar {
width: 22px;
height: 22px;
display: inline-grid;
place-items: center;
border: 1px solid var(--idp-border);
border-radius: 50%;
background: var(--idp-card-2);
color: var(--idp-accent-hover);
font-family: var(--idp-mono);
font-size: 9.5px;
font-weight: 700;
}
.row-name {
color: var(--idp-fg);
font-weight: 500;
}
.row-email, .dim {
color: var(--idp-muted-fg);
font-family: var(--idp-mono);
font-size: 11px;
}
.feed-item {
display: grid;
grid-template-columns: 14px 1fr auto;
gap: 12px;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--idp-border-soft);
}
.feed-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--idp-accent-hover);
}
.feed-dot.ok {
background: var(--idp-ok);
}
.feed-text {
color: var(--idp-fg-3, var(--idp-muted-fg));
font-size: 12.5px;
}
.feed-text strong {
color: var(--idp-fg);
font-weight: 500;
}
.feed-meta {
color: color-mix(in srgb, var(--idp-muted-fg), transparent 35%);
font-family: var(--idp-mono);
font-size: 10.5px;
}
.bottom {
height: 28px;
padding: 0 14px;
border-top: 1px solid var(--idp-border-soft);
border-bottom: 0;
background: var(--idp-bg);
}
.bottom .divider {
height: 12px;
}
.grow {
flex: 1;
}
@media (max-width: 900px) {
.shell {
grid-template-columns: 1fr;
}
aside, .search {
display: none;
}
.stats {
grid-template-columns: repeat(2, 1fr);
}
.grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
.stats {
grid-template-columns: 1fr;
}
.head {
align-items: flex-start;
flex-direction: column;
}
th:nth-child(3), td:nth-child(3), th:nth-child(4), td:nth-child(4) {
display: none;
}
}
`,
];
private stats: TDashboardStat[] = [
{ label: 'Identities', value: '2,847', delta: '↑ 12% wk', sub: '142 added this week', accent: 'var(--idp-chart-1)', sparkColor: 'var(--idp-spark-up)', spark: [10, 12, 11, 14, 13, 16, 15, 18, 19] },
{ label: 'Active devices', value: '9,140', delta: '↑ 4.2%', sub: '3.2 avg / identity', accent: 'var(--idp-chart-2)', sparkColor: 'var(--idp-spark-up)', spark: [12, 13, 11, 14, 13, 15, 14, 16, 17] },
{ label: 'Avg approval', value: '0.8', unit: 's', delta: '↓ 60ms faster', sub: 'p95 - all regions', accent: 'var(--idp-chart-5)', sparkColor: 'var(--idp-spark-info)', spark: [16, 14, 17, 12, 15, 13, 11, 9, 7] },
{ label: 'Cardano anchors', value: '12,408', delta: 'synced 4s ago', sub: 'block #9 841 222', accent: 'var(--idp-info)', sparkColor: 'var(--idp-spark-up)', spark: [8, 9, 11, 10, 13, 12, 15, 16, 18], live: true },
];
private approvals = [
['Jane Doe', 'jane@lossless.com', 'OAuth - GitHub', 'iPhone 15 Pro', 'approved', 'ok'],
['Alex Brown', 'alex@lossless.com', 'CLI login', 'MacBook Pro', 'pending', 'warn'],
['Sam Chen', 'sam@lossless.com', 'NFC tap - door 4F', 'iPhone 14', 'approved', 'ok'],
['Unknown device', 'Lagos - NG', 'Web login', 'Chrome 132', 'denied', 'error'],
['Maria K.', 'maria@lossless.com', 'Key rotation', 'Apple Watch S9', 'on-chain', 'accent'],
];
private feed = [
['Identity created', 'did:idp:0x9b12...f034', 'block #9 841 222', ''],
['Anchor confirmed', '12 blocks deep', '2m', 'ok'],
['Key rotation', 'did:idp:0x4a3f...c819', 'block #9 841 221', ''],
['OAuth scope updated', 'github repo:read', '5m', 'ok'],
['Device registered', 'MacBook Pro pending', '7m', ''],
];
private workspaceNav = [
['Overview', 'grid'],
['Identities', 'user'],
['Approvals', 'bell'],
['OAuth clients', 'key'],
['Devices', 'monitor'],
['Audit log', 'clock'],
];
private chainNav = [
['Cardano sync', 'wallet'],
['Anchors', 'shield'],
];
private renderSparkline(data: number[], color: string): TemplateResult {
const max = Math.max(...data);
const min = Math.min(...data);
const range = max - min || 1;
const width = 100;
const height = 22;
const points = data.map((valueArg, indexArg) => {
const x = (indexArg / (data.length - 1)) * width;
const y = height - ((valueArg - min) / range) * (height - 4) - 2;
return `${x},${y}`;
}).join(' ');
const area = `0,${height} ${points} ${width},${height}`;
return html`<svg viewBox="0 0 ${width} ${height}" preserveAspectRatio="none"><polygon points=${area} fill=${color} opacity="0.12"></polygon><polyline points=${points} fill="none" stroke=${color} stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></polyline></svg>`;
}
private renderStat(statArg: TDashboardStat): TemplateResult {
return html`
<div class="stat" style="--stat-accent:${statArg.accent}">
<div class="stat-label">${statArg.label}</div>
<div class="stat-val">${statArg.value}${statArg.unit ? html`<span>${statArg.unit}</span>` : html``}</div>
<div class="stat-sub">${statArg.sub}</div>
<div class="stat-foot"><span class="delta">${statArg.live ? html`<span class="live-dot"></span>` : html``}${statArg.delta}</span><div class="sparkline">${this.renderSparkline(statArg.spark, statArg.sparkColor)}</div></div>
</div>
`;
}
public render(): TemplateResult {
return html`
<div class="dash" theme="dark">
<div class="chrome">
<span class="tdot red"></span><span class="tdot yellow"></span><span class="tdot green"></span>
<span class="url"><idp-icon name="lock" size="11" style="color: var(--idp-ok)"></idp-icon> console.idp.global / dashboard</span>
<span class="status"><span class="live-dot"></span>eu-west-1 - 38ms</span>
</div>
<div class="appbar">
<div class="appbar-left">
<span class="logo">idp<span class="logo-dot"></span>global</span>
<span class="divider"></span>
<span class="org"><span class="avatar-sm">L</span>Lossless GmbH</span>
</div>
<div class="appbar-right">
<span class="search">Search identities, devices <span class="kbd">Cmd+K</span></span>
<span class="user-avatar">AM</span>
</div>
</div>
<div class="shell">
<aside>
<div class="side-label">Workspace</div>
${this.workspaceNav.map((itemArg, indexArg) => html`
<span class="side-nav ${indexArg === 0 ? 'active' : ''}"><span class="nav-icon"><idp-icon name=${itemArg[1] as any} size="13"></idp-icon></span>${itemArg[0]}</span>
`)}
<div class="side-label">On-chain</div>
${this.chainNav.map((itemArg) => html`<span class="side-nav"><span class="nav-icon"><idp-icon name=${itemArg[1] as any} size="13"></idp-icon></span>${itemArg[0]}</span>`)}
</aside>
<main>
<div class="head">
<div>
<h3>Overview</h3>
<div class="sub">Identity activity across <span style="color: var(--idp-accent-hover); font-family: var(--idp-mono)">@lossless</span> - last 7 days</div>
</div>
<div class="actions"><idp-button variant="ghost" size="sm">Export</idp-button><idp-button variant="accent" size="sm">New identity</idp-button></div>
</div>
<div class="stats">
${this.stats.map((statArg) => this.renderStat(statArg))}
</div>
<div class="grid">
<section class="card">
<div class="card-head"><span class="card-title">Recent approvals</span><idp-badge>142 total</idp-badge></div>
<table>
<thead><tr><th>User</th><th>Action</th><th>Device</th><th>Status</th></tr></thead>
<tbody>
${this.approvals.map((rowArg) => html`
<tr>
<td><div class="user"><span class="row-avatar">${rowArg[0].slice(0, 2).toUpperCase()}</span><div><div class="row-name">${rowArg[0]}</div><div class="row-email">${rowArg[1]}</div></div></div></td>
<td>${rowArg[2]}</td>
<td><span class="dim">${rowArg[3]}</span></td>
<td><idp-badge variant=${rowArg[5] as any}>${rowArg[4]}</idp-badge></td>
</tr>
`)}
</tbody>
</table>
</section>
<section class="card">
<div class="card-head"><span class="card-title">Cardano feed</span><idp-badge variant="accent"><span class="live-dot"></span>live</idp-badge></div>
${this.feed.map((itemArg) => html`
<div class="feed-item"><span class="feed-dot ${itemArg[3]}"></span><div class="feed-text"><strong>${itemArg[0]}</strong> - ${itemArg[1]}</div><span class="feed-meta">${itemArg[2]}</span></div>
`)}
</section>
</div>
</main>
</div>
<div class="bottom"><span><span class="live-dot"></span>API - 38ms</span><span class="divider"></span><span>v3.81.0</span><span class="grow"></span><span>block #9 841 222 - confirmed</span></div>
</div>
`;
}
}
+263
View File
@@ -0,0 +1,263 @@
import {
DeesElement,
html,
property,
customElement,
css,
type TemplateResult,
} from '@design.estate/dees-element';
import * as lucideIcons from 'lucide';
import {
Activity,
ArrowUp,
Bell,
Bolt,
Box,
Building2,
Check,
ChevronDown,
ChevronRight,
Clock,
Cloud,
Copy,
CreditCard,
Fingerprint,
Globe,
Grid2x2,
Home,
Key,
Laptop,
Lock,
LogOut,
Mail,
MapPin,
Monitor,
MonitorSmartphone,
Nfc,
Phone,
Plus,
Power,
QrCode,
Search,
Settings,
Shield,
SmartphoneNfc,
SquarePen,
Trash2,
TriangleAlert,
User,
Users,
Wallet,
X,
createElement,
type IconNode,
} from 'lucide';
import { idpElementStyles } from './tokens.js';
export type TIdpIconName =
| 'activity'
| 'alert'
| 'alert-triangle'
| 'arrow-up'
| 'bell'
| 'bolt'
| 'box'
| 'building'
| 'building2'
| 'building-2'
| 'check'
| 'chevron'
| 'chevron-down'
| 'chevron-right'
| 'clock'
| 'cloud'
| 'copy'
| 'credit'
| 'device'
| 'edit'
| 'fingerprint'
| 'gear'
| 'globe'
| 'grid'
| 'home'
| 'key'
| 'laptop'
| 'location'
| 'lock'
| 'logout'
| 'mail'
| 'monitor'
| 'monitor-smartphone'
| 'nfc'
| 'phone'
| 'plus'
| 'power'
| 'qr'
| 'search'
| 'settings'
| 'shield'
| 'smartphone-nfc'
| 'trash'
| 'user'
| 'users'
| 'wallet'
| 'waveform'
| 'x'
| `lucide:${string}`;
declare global {
interface HTMLElementTagNameMap {
'idp-icon': IdpIcon;
}
}
const iconNodes: Record<string, IconNode> = {
activity: Activity,
alert: TriangleAlert,
'alert-triangle': TriangleAlert,
'arrow-up': ArrowUp,
bell: Bell,
bolt: Bolt,
box: Box,
building: Building2,
building2: Building2,
'building-2': Building2,
check: Check,
chevron: ChevronRight,
'chevron-down': ChevronDown,
'chevron-right': ChevronRight,
clock: Clock,
cloud: Cloud,
copy: Copy,
credit: CreditCard,
device: MonitorSmartphone,
edit: SquarePen,
fingerprint: Fingerprint,
gear: Settings,
globe: Globe,
grid: Grid2x2,
home: Home,
key: Key,
laptop: Laptop,
location: MapPin,
lock: Lock,
logout: LogOut,
mail: Mail,
monitor: Monitor,
'monitor-smartphone': MonitorSmartphone,
nfc: Nfc,
phone: Phone,
plus: Plus,
power: Power,
qr: QrCode,
search: Search,
settings: Settings,
shield: Shield,
'smartphone-nfc': SmartphoneNfc,
trash: Trash2,
user: User,
users: Users,
wallet: Wallet,
waveform: Activity,
x: X,
};
const toKebab = (valueArg: string): string => valueArg
.replace(/^lucide:/, '')
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.toLowerCase();
const toLucideExportName = (valueArg: string): string => valueArg
.replace(/^lucide:/i, '')
.split(/[-_: ]+/)
.filter(Boolean)
.map((partArg) => `${partArg.charAt(0).toUpperCase()}${partArg.slice(1)}`)
.join('');
@customElement('idp-icon')
export class IdpIcon extends DeesElement {
public static demo = () => html`<idp-icon name="shield"></idp-icon>`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor name: TIdpIconName = 'shield';
@property({ type: Number })
public accessor size = 18;
private lastRenderKey = '';
public static styles = [
...idpElementStyles,
css`
:host {
display: inline-flex;
align-items: center;
justify-content: center;
color: currentColor;
line-height: 0;
vertical-align: middle;
}
#iconContainer {
width: var(--icon-size);
height: var(--icon-size);
}
#iconContainer svg {
display: block;
width: 100%;
height: 100%;
fill: none;
stroke: currentColor;
stroke-width: 1.75;
stroke-linecap: round;
stroke-linejoin: round;
}
`,
];
private resolveIconNode(): IconNode {
const rawName = String(this.name || 'shield');
const iconName = toKebab(rawName);
const aliasNode = iconNodes[iconName];
if (aliasNode) {
return aliasNode;
}
const exportName = toLucideExportName(rawName);
const lucideNode = (lucideIcons as Record<string, unknown>)[exportName];
if (Array.isArray(lucideNode)) {
return lucideNode as IconNode;
}
return Shield;
}
public render(): TemplateResult {
return html`
<div id="iconContainer" style="--icon-size: ${this.size}px"></div>
`;
}
public updated(): void {
const renderKey = `${this.name}:${this.size}`;
if (this.lastRenderKey === renderKey) {
return;
}
this.lastRenderKey = renderKey;
const container = this.shadowRoot?.querySelector('#iconContainer');
if (!container) {
return;
}
container.innerHTML = '';
const iconElement = createElement(this.resolveIconNode(), {
color: 'currentColor',
size: this.size,
strokeWidth: 1.75,
});
iconElement.setAttribute('aria-hidden', 'true');
container.appendChild(iconElement);
}
}
+192
View File
@@ -0,0 +1,192 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
import './idp-approval-card.js';
import './idp-badge.js';
import './idp-button.js';
import './idp-icon.js';
import './idp-mobile-frame.js';
declare global {
interface HTMLElementTagNameMap {
'idp-inbox-preview': IdpInboxPreview;
}
}
@customElement('idp-inbox-preview')
export class IdpInboxPreview extends DeesElement {
public static demo = () => html`<idp-inbox-preview></idp-inbox-preview>`;
public static demoGroups = ['idp.global v3 approval surfaces'];
@property({ type: Boolean, reflect: true })
public accessor dark = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
idp-mobile-frame {
--idp-bg: #ffffff;
}
:host([dark]) idp-mobile-frame {
--idp-bg: #09090b;
--idp-fg: #fafafa;
}
.screen {
height: 100%;
background: var(--idp-bg);
color: var(--idp-fg);
box-sizing: border-box;
padding-top: 58px;
display: flex;
flex-direction: column;
}
.header {
padding: 0 20px 14px;
border-bottom: 1px solid var(--idp-border);
}
.brandline {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.brand {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 650;
}
.brandmark {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
background: var(--idp-primary);
color: var(--idp-primary-fg);
}
h2 {
margin: 0;
font-size: 24px;
font-weight: 760;
letter-spacing: -0.04em;
}
.summary {
display: flex;
align-items: center;
gap: 8px;
margin-top: 6px;
color: var(--idp-muted-fg);
font-size: 13px;
}
.list {
display: grid;
gap: 10px;
padding: 14px 16px 120px;
overflow: auto;
}
.earlier {
margin: 10px 0 2px;
color: var(--idp-muted-fg);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.row {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 12px;
border: 1px solid var(--idp-border);
border-radius: 10px;
font-size: 13px;
}
.row-icon {
width: 26px;
height: 26px;
border-radius: 7px;
background: var(--idp-muted);
color: var(--idp-ok);
display: flex;
align-items: center;
justify-content: center;
}
.row-sub {
color: var(--idp-muted-fg);
font-size: 12px;
margin-top: 1px;
}
.tabbar {
position: absolute;
left: 12px;
right: 12px;
bottom: 24px;
z-index: 80;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
padding: 8px;
border: 1px solid color-mix(in srgb, var(--idp-border), transparent 20%);
border-radius: 22px;
background: color-mix(in srgb, var(--idp-card), transparent 8%);
box-shadow: 0 16px 36px rgb(0 0 0 / 0.16);
backdrop-filter: blur(24px) saturate(170%);
}
.tab {
display: grid;
place-items: center;
gap: 3px;
min-height: 44px;
color: var(--idp-muted-fg);
font-size: 10px;
font-weight: 600;
}
.tab.active {
color: var(--idp-accent);
}
`,
];
public render(): TemplateResult {
return html`
<idp-mobile-frame ?dark=${this.dark}>
<div class="screen">
<header class="header">
<div class="brandline">
<div class="brand"><span class="brandmark"><idp-icon name="shield" size="14"></idp-icon></span>idp.global</div>
<idp-button variant="ghost" size="sm" icon="search" aria-label="Search"></idp-button>
</div>
<h2>Inbox</h2>
<div class="summary"><idp-badge variant="ok">3 pending</idp-badge><span>oldest 8 min ago</span></div>
</header>
<div class="list">
<idp-approval-card primary app-name="GitHub" app-initials="GH" app-color="#24292F" request-text="Sign in to github.com" location="Berlin · DE" device="Safari · MBP" time-label="now"></idp-approval-card>
<idp-approval-card app-name="Lufthansa.com" app-initials="LH" app-color="#05164D" request-text="Verify identity" location="Berlin · DE" device="iPhone 15 Pro" time-label="2m"></idp-approval-card>
<idp-approval-card app-name="Hetzner Cloud" app-initials="HZ" app-color="#D50C2D" request-text="Sign in to console" location="Falkenstein · DE" device="Firefox · Windows" risk="warning" time-label="8m"></idp-approval-card>
<div class="earlier">Earlier today</div>
${['Notion · Approved 11:42', 'Apple ID · Approved 09:18', 'reddit.com · Denied 08:57'].map((itemArg) => html`
<div class="row">
<div class="row-icon"><idp-icon name="check" size="15"></idp-icon></div>
<div>
<div>${itemArg.split(' · ')[0]}</div>
<div class="row-sub">${itemArg.split(' · ')[1]}</div>
</div>
</div>
`)}
</div>
<nav class="tabbar">
<div class="tab active"><idp-icon name="bell" size="18"></idp-icon>Inbox</div>
<div class="tab"><idp-icon name="clock" size="18"></idp-icon>History</div>
<div class="tab"><idp-icon name="device" size="18"></idp-icon>Devices</div>
<div class="tab"><idp-icon name="user" size="18"></idp-icon>Identity</div>
</nav>
</div>
</idp-mobile-frame>
`;
}
}
+101
View File
@@ -0,0 +1,101 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
declare global {
interface HTMLElementTagNameMap {
'idp-input': IdpInput;
}
}
@customElement('idp-input')
export class IdpInput extends DeesElement {
public static demo = () => html`<idp-input label="Email" placeholder="user@example.com"></idp-input>`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: String })
public accessor label = '';
@property({ type: String })
public accessor hint = '';
@property({ type: String })
public accessor value = '';
@property({ type: String })
public accessor placeholder = '';
@property({ type: String })
public accessor type = 'text';
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
label {
display: grid;
gap: 6px;
}
.label {
color: var(--idp-fg);
font-size: 13px;
font-weight: 500;
}
input {
width: 100%;
height: 36px;
box-sizing: border-box;
padding: 0 10px;
border: 1px solid var(--idp-border);
border-radius: 8px;
outline: none;
background: var(--idp-card);
color: var(--idp-fg);
font-family: var(--idp-font);
font-size: 13px;
transition: border-color 120ms ease, box-shadow 120ms ease;
}
input:focus {
border-color: var(--idp-accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--idp-accent), transparent 86%);
}
input:disabled {
opacity: 0.5;
}
.hint {
color: var(--idp-muted-fg);
font-size: 12px;
line-height: 1.4;
}
`,
];
private handleInput(eventArg: Event) {
this.value = (eventArg.target as HTMLInputElement).value;
this.dispatchEvent(new CustomEvent('idp-input-change', {
detail: { value: this.value },
bubbles: true,
composed: true,
}));
}
public render(): TemplateResult {
return html`
<label>
${this.label ? html`<span class="label">${this.label}</span>` : html``}
<input
.value=${this.value}
type=${this.type}
placeholder=${this.placeholder}
?disabled=${this.disabled}
@input=${this.handleInput}
/>
${this.hint ? html`<span class="hint">${this.hint}</span>` : html``}
</label>
`;
}
}
+165
View File
@@ -0,0 +1,165 @@
import { DeesElement, html, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
import './idp-button.js';
import './idp-dashboard-window.js';
import './idp-icon.js';
declare global {
interface HTMLElementTagNameMap {
'idp-landing-hero': IdpLandingHero;
}
}
@customElement('idp-landing-hero')
export class IdpLandingHero extends DeesElement {
public static demo = () => html`<idp-landing-hero></idp-landing-hero>`;
public static demoGroups = ['idp.global v3 composed surfaces'];
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
.hero {
position: relative;
overflow: hidden;
background: #0a0a0a;
color: #fafafa;
border-bottom: 1px solid #1c1c1c;
}
.grid {
position: absolute;
inset: 0;
background-image: linear-gradient(to right, rgba(255,255,255,0.025) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.025) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 30%, transparent 70%);
pointer-events: none;
}
.glow {
position: absolute;
top: -220px;
left: 50%;
width: 900px;
height: 600px;
transform: translateX(-50%);
background: radial-gradient(ellipse, rgba(59,130,246,0.18) 0%, transparent 60%);
pointer-events: none;
}
.inner {
position: relative;
z-index: 1;
max-width: 1240px;
margin: 0 auto;
padding: 96px 32px 0;
text-align: center;
}
.badge {
display: inline-flex;
align-items: center;
gap: 7px;
margin-bottom: 28px;
padding: 5px 12px 5px 8px;
border: 1px solid #262626;
border-radius: 999px;
background: rgba(255,255,255,0.04);
color: hsl(0 0% 70%);
font-size: 12px;
}
.pill {
padding: 2px 7px;
border-radius: 999px;
background: rgba(59,130,246,0.18);
color: #60a5fa;
font-family: var(--idp-mono);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
}
h1 {
margin: 0 0 24px;
font-size: clamp(44px, 6.5vw, 78px);
font-family: var(--idp-display);
font-weight: 700;
letter-spacing: -0.035em;
line-height: 0.96;
}
h1 em {
color: #60a5fa;
font-family: var(--idp-serif);
font-style: italic;
font-weight: 400;
}
.sub {
max-width: 660px;
margin: 0 auto 36px;
color: hsl(0 0% 70%);
font-size: clamp(16px, 1.6vw, 19px);
line-height: 1.55;
}
.actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
align-items: center;
justify-content: center;
margin-bottom: 20px;
}
.fineprint {
color: hsl(0 0% 28%);
font-family: var(--idp-mono);
font-size: 11px;
letter-spacing: 0.04em;
}
.fineprint span + span::before {
content: '*';
margin: 0 10px;
}
.product {
position: relative;
max-width: 1180px;
margin: 72px auto 0;
padding: 0 32px;
}
.product-glow {
position: absolute;
inset: 30% 10% -10%;
background: radial-gradient(ellipse, rgba(59,130,246,0.25) 0%, transparent 60%);
filter: blur(40px);
pointer-events: none;
}
idp-dashboard-window {
position: relative;
z-index: 1;
}
@media (max-width: 760px) {
.inner {
padding: 72px 20px 0;
}
}
`,
];
public render(): TemplateResult {
return html`
<section class="hero">
<div class="grid"></div>
<div class="glow"></div>
<div class="inner">
<div class="badge"><span class="pill">v3.81</span>Cardano-anchored identity, now self-hostable</div>
<h1>One identity.<br/><em>Any scale.</em> Yours forever.</h1>
<p class="sub">An open identity provider for everyone, from a single person to a global enterprise. Anchored to the Cardano blockchain so it can never be erased, taken away, or quietly deprecated.</p>
<div class="actions">
<idp-button variant="accent" size="lg" icon="shield">Claim your identity - free</idp-button>
<idp-button variant="ghost" size="lg" icon="globe">View source</idp-button>
</div>
<div class="fineprint"><span>MIT licensed</span><span>Self-hostable</span><span>No credit card</span><span>Cardano mainnet</span></div>
<div class="product">
<div class="product-glow"></div>
<idp-dashboard-window dark></idp-dashboard-window>
</div>
</div>
</section>
`;
}
}
+668
View File
@@ -0,0 +1,668 @@
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>
`;
}
}
+137
View File
@@ -0,0 +1,137 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
declare global {
interface HTMLElementTagNameMap {
'idp-mobile-frame': IdpMobileFrame;
}
}
@customElement('idp-mobile-frame')
export class IdpMobileFrame extends DeesElement {
public static demo = () => html`
<idp-mobile-frame>
<div style="height: 100%; background: #fff; padding: 72px 20px 20px; box-sizing: border-box;">Screen content</div>
</idp-mobile-frame>
`;
public static demoGroups = ['idp.global v3 device frames'];
@property({ type: String })
public accessor time = '9:41';
@property({ type: Boolean, reflect: true })
public accessor dark = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: inline-block;
}
.device {
position: relative;
width: 402px;
height: 874px;
max-width: 100%;
border-radius: 48px;
overflow: hidden;
background: var(--idp-bg);
box-shadow: 0 40px 80px rgb(0 0 0 / 0.18), 0 0 0 1px rgb(0 0 0 / 0.12);
}
.island {
position: absolute;
top: 11px;
left: 50%;
z-index: 50;
width: 126px;
height: 37px;
transform: translateX(-50%);
border-radius: 24px;
background: #000;
}
.status {
position: absolute;
inset: 0 0 auto;
z-index: 40;
height: 58px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 28px 0;
box-sizing: border-box;
color: var(--idp-fg);
font-size: 15px;
font-weight: 650;
pointer-events: none;
}
.status-icons {
display: inline-flex;
align-items: center;
gap: 7px;
}
.bar {
width: 4px;
border-radius: 999px;
background: currentColor;
}
.screen {
height: 100%;
overflow: hidden;
}
.home {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 60;
height: 34px;
display: flex;
align-items: flex-end;
justify-content: center;
padding-bottom: 8px;
pointer-events: none;
}
.home::before {
content: '';
width: 139px;
height: 5px;
border-radius: 100px;
background: color-mix(in srgb, var(--idp-fg), transparent 72%);
}
@media (max-width: 520px) {
:host {
display: block;
width: 100%;
}
.device {
width: 100%;
height: auto;
aspect-ratio: 402 / 874;
border-radius: 0;
}
.island {
display: none;
}
}
`,
];
public render(): TemplateResult {
return html`
<div class="device">
<div class="island"></div>
<div class="status">
<span>${this.time}</span>
<span class="status-icons" aria-hidden="true">
<span class="bar" style="height: 5px"></span>
<span class="bar" style="height: 8px"></span>
<span class="bar" style="height: 11px"></span>
<span style="width: 24px; height: 12px; border: 1.5px solid currentColor; border-radius: 4px;"></span>
</span>
</div>
<div class="screen"><slot></slot></div>
<div class="home"></div>
</div>
`;
}
}
+287
View File
@@ -0,0 +1,287 @@
import { DeesElement, html, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
import './idp-button.js';
import './idp-icon.js';
import './idp-inbox-preview.js';
import './idp-mobile-frame.js';
declare global {
interface HTMLElementTagNameMap {
'idp-mobile-showcase': IdpMobileShowcase;
}
}
@customElement('idp-mobile-showcase')
export class IdpMobileShowcase extends DeesElement {
public static demo = () => html`<idp-mobile-showcase></idp-mobile-showcase>`;
public static demoGroups = ['idp.global v3 full pages'];
public static styles = [
...idpElementStyles,
css`
:host {
display: block;
}
.showcase {
min-height: 100vh;
padding: 56px;
background: radial-gradient(circle at 1px 1px, rgba(0,0,0,0.08) 1px, transparent 0) 0 0 / 24px 24px, #fafafa;
color: #09090b;
}
.head {
max-width: 1180px;
margin: 0 auto 44px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
margin-bottom: 16px;
padding: 4px 10px;
border: 1px solid #e4e4e7;
border-radius: 999px;
background: #fff;
color: #52525b;
font-size: 11px;
font-weight: 500;
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #16a34a;
}
h1 {
max-width: 900px;
margin: 0 0 14px;
font-family: var(--idp-display);
font-size: clamp(36px, 5vw, 56px);
font-weight: 750;
letter-spacing: -0.035em;
line-height: 1.05;
}
p {
max-width: 680px;
margin: 0;
color: #52525b;
font-size: 16px;
line-height: 1.55;
}
.tokens {
display: flex;
flex-wrap: wrap;
gap: 24px;
margin-top: 24px;
padding: 16px;
border: 1px solid #e4e4e7;
border-radius: 12px;
background: #fff;
}
.token-label {
margin-bottom: 4px;
color: #71717a;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.token-value {
display: flex;
align-items: center;
gap: 8px;
color: #18181b;
font-size: 13px;
font-weight: 550;
}
.swatch {
width: 18px;
height: 18px;
border-radius: 5px;
background: var(--swatch);
border: 1px solid #e4e4e7;
}
.section {
max-width: 1180px;
margin: 0 auto 56px;
}
.section-title {
margin-bottom: 18px;
color: #71717a;
font-family: var(--idp-mono);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.phones {
display: grid;
grid-template-columns: repeat(2, minmax(300px, 402px));
gap: 28px;
align-items: start;
}
.multi {
display: grid;
grid-template-columns: 280px minmax(0, 1fr) 360px;
gap: 28px;
align-items: start;
}
.watch, .ipad, .mac {
border: 1px solid #e4e4e7;
background: #fff;
box-shadow: 0 20px 50px rgba(0,0,0,0.08);
}
.watch {
width: 236px;
height: 286px;
display: grid;
place-items: center;
border-radius: 60px;
background: #09090b;
color: #fafafa;
}
.watch-screen {
width: 178px;
display: grid;
gap: 12px;
text-align: center;
}
.watch-app {
color: #a1a1aa;
font-family: var(--idp-mono);
font-size: 10px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.watch-title {
font-size: 18px;
font-weight: 700;
}
.watch-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.watch-actions button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
height: 34px;
border: 1px solid #27272a;
border-radius: 999px;
background: #18181b;
color: #fafafa;
}
.watch-actions .approve {
background: #3b82f6;
border-color: #3b82f6;
}
.ipad {
min-height: 520px;
overflow: hidden;
border-radius: 26px;
}
.ipad-shell {
display: grid;
grid-template-columns: 220px 1fr;
min-height: 520px;
}
.ipad-sidebar {
padding: 18px;
border-right: 1px solid #e4e4e7;
background: #f8f8f7;
}
.ipad-main {
padding: 22px;
}
.ipad-card {
display: flex;
align-items: flex-start;
gap: 10px;
margin-top: 16px;
padding: 16px;
border: 1px solid #e4e4e7;
border-radius: 12px;
}
.mac {
overflow: hidden;
border-radius: 14px;
}
.mac-bar {
display: flex;
gap: 6px;
padding: 11px 14px;
border-bottom: 1px solid #e4e4e7;
}
.tdot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.mac-body {
display: grid;
gap: 12px;
padding: 18px;
}
.mac-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px;
border: 1px solid #e4e4e7;
border-radius: 10px;
}
.row-label {
display: inline-flex;
align-items: center;
gap: 8px;
}
@media (max-width: 1120px) {
.multi {
grid-template-columns: 1fr;
}
}
@media (max-width: 900px) {
.showcase {
padding: 28px;
}
.phones {
grid-template-columns: 1fr;
}
}
`,
];
public render(): TemplateResult {
return html`
<div class="showcase">
<header class="head">
<div class="badge"><span class="dot"></span>Mobile redesign - v3 - shadcn tokens</div>
<h1>A personal identity provider, built across every device you carry.</h1>
<p>Same four-platform architecture: flat surfaces, 1px borders, neutral palette, and blue accent used only where action is expected.</p>
<div class="tokens">
${[
['Primary', '#18181b', 'zinc-900'],
['Accent', '#0050b9', 'idp blue'],
['Muted', '#f4f4f2', 'paper muted'],
['Border', '#e4e4e7', 'zinc-200'],
].map((tokenArg) => html`<div><div class="token-label">${tokenArg[0]}</div><div class="token-value"><span class="swatch" style="--swatch:${tokenArg[1]}"></span>${tokenArg[2]}</div></div>`)}
</div>
</header>
<section class="section">
<div class="section-title">iPhone</div>
<div class="phones"><idp-inbox-preview></idp-inbox-preview><idp-inbox-preview dark></idp-inbox-preview></div>
</section>
<section class="section">
<div class="section-title">Watch, iPad, Mac</div>
<div class="multi">
<div class="watch"><div class="watch-screen"><div class="watch-app">idp.global</div><idp-icon name="shield" size="28" style="margin:0 auto;color:#60a5fa"></idp-icon><div class="watch-title">GitHub wants access</div><div style="color:#a1a1aa;font-size:12px;">repo:read - Berlin</div><div class="watch-actions"><button><idp-icon name="x" size="13"></idp-icon>Deny</button><button class="approve"><idp-icon name="check" size="13"></idp-icon>Approve</button></div></div></div>
<div class="ipad"><div class="ipad-shell"><aside class="ipad-sidebar"><strong>Inbox</strong><p>3 pending approvals</p><div class="ipad-card"><idp-icon name="globe" size="16"></idp-icon><div><strong>GitHub OAuth</strong><br/><span style="color:#71717a">repo:read - now</span></div></div><div class="ipad-card"><idp-icon name="cloud" size="16"></idp-icon><div><strong>Hetzner Cloud</strong><br/><span style="color:#71717a">new network - 8m</span></div></div></aside><main class="ipad-main"><h2>Approval detail</h2><p>Full context before a sensitive action is approved.</p><div class="ipad-card"><idp-icon name="laptop" size="16"></idp-icon><div><strong>Device</strong><br/>MacBook Pro - Safari - Berlin, DE</div></div><div class="ipad-card"><idp-icon name="key" size="16"></idp-icon><div><strong>Requested scopes</strong><br/>openid, profile, email, repo:read</div></div></main></div></div>
<div class="mac"><div class="mac-bar"><span class="tdot" style="background:#ff5f57"></span><span class="tdot" style="background:#ffbd2e"></span><span class="tdot" style="background:#28c840"></span></div><div class="mac-body"><strong>Menu bar approvals</strong><div class="mac-row"><span class="row-label"><idp-icon name="globe" size="15"></idp-icon>GitHub OAuth</span><idp-button variant="accent" size="sm" icon="check">Approve</idp-button></div><div class="mac-row"><span class="row-label"><idp-icon name="nfc" size="15"></idp-icon>NFC tap - door 4F</span><idp-button variant="ghost" size="sm" icon="chevron-right">Review</idp-button></div><div class="mac-row"><span class="row-label"><idp-icon name="key" size="15"></idp-icon>Key rotation</span><idp-button variant="ghost" size="sm" icon="shield">Confirm</idp-button></div></div></div>
</div>
</section>
</div>
`;
}
}
+77
View File
@@ -0,0 +1,77 @@
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
import { idpElementStyles } from './tokens.js';
declare global {
interface HTMLElementTagNameMap {
'idp-toggle': IdpToggle;
}
}
@customElement('idp-toggle')
export class IdpToggle extends DeesElement {
public static demo = () => html`<idp-toggle checked></idp-toggle>`;
public static demoGroups = ['idp.global v3 primitives'];
@property({ type: Boolean, reflect: true })
public accessor checked = false;
@property({ type: Boolean, reflect: true })
public accessor disabled = false;
public static styles = [
...idpElementStyles,
css`
:host {
display: inline-flex;
}
button {
width: 36px;
height: 20px;
border: none;
border-radius: 999px;
padding: 2px;
background: var(--idp-border);
cursor: pointer;
transition: background 150ms ease;
}
:host([checked]) button {
background: var(--idp-accent);
}
:host([disabled]) button {
opacity: 0.5;
cursor: not-allowed;
}
.knob {
width: 16px;
height: 16px;
border-radius: 999px;
background: #fff;
box-shadow: 0 1px 3px rgb(0 0 0 / 0.22);
transition: transform 150ms ease;
}
:host([checked]) .knob {
transform: translateX(16px);
}
`,
];
private toggle() {
if (this.disabled) {
return;
}
this.checked = !this.checked;
this.dispatchEvent(new CustomEvent('idp-toggle-change', {
detail: { checked: this.checked },
bubbles: true,
composed: true,
}));
}
public render(): TemplateResult {
return html`
<button role="switch" aria-checked=${this.checked} ?disabled=${this.disabled} @click=${this.toggle}>
<div class="knob"></div>
</button>
`;
}
}
+15
View File
@@ -0,0 +1,15 @@
export * from './tokens.js';
export * from './idp-icon.js';
export * from './idp-button.js';
export * from './idp-badge.js';
export * from './idp-card.js';
export * from './idp-input.js';
export * from './idp-toggle.js';
export * from './idp-approval-card.js';
export * from './idp-mobile-frame.js';
export * from './idp-inbox-preview.js';
export * from './idp-dashboard-window.js';
export * from './idp-admin-shell.js';
export * from './idp-landing-hero.js';
export * from './idp-landing-page.js';
export * from './idp-mobile-showcase.js';
+120
View File
@@ -0,0 +1,120 @@
import { css, cssManager } from '@design.estate/dees-element';
export const idpAccent = '#3B82F6';
export const idpTheme = {
bg: 'var(--idp-bg)',
fg: 'var(--idp-fg)',
muted: 'var(--idp-muted)',
mutedFg: 'var(--idp-muted-fg)',
border: 'var(--idp-border)',
card: 'var(--idp-card)',
primary: 'var(--idp-primary)',
primaryFg: 'var(--idp-primary-fg)',
accent: 'var(--idp-accent)',
accentHover: 'var(--idp-accent-hover)',
accentSoft: 'var(--idp-accent-soft)',
info: 'var(--idp-info)',
destructive: 'var(--idp-destructive)',
ok: 'var(--idp-ok)',
warn: 'var(--idp-warn)',
radius: 'var(--idp-radius)',
};
export const idpBaseStyles = css`
:host {
--idp-bg: #fafaf9;
--idp-bg-2: #f4f4f2;
--idp-fg: #0a0a0a;
--idp-fg-2: #3f3f46;
--idp-fg-3: #52525b;
--idp-muted: #f1f1ef;
--idp-muted-fg: #71717a;
--idp-border: #e4e4e7;
--idp-border-soft: #ededec;
--idp-border-strong: #d4d4d8;
--idp-card: #ffffff;
--idp-card-2: #f8f8f7;
--idp-primary: #18181b;
--idp-primary-fg: #fafafa;
--idp-accent: #0050b9;
--idp-accent-hover: #0069f2;
--idp-accent-fg: #ffffff;
--idp-accent-soft: #e6effb;
--idp-destructive: #ef4444;
--idp-ok: #16a34a;
--idp-ok-bg: #f0fdf4;
--idp-ok-border: #bbf7d0;
--idp-warn: #b45309;
--idp-warn-bg: #fef9c3;
--idp-warn-border: #fde68a;
--idp-error: #dc2626;
--idp-error-bg: #fef2f2;
--idp-error-border: #fecaca;
--idp-info: #1e40af;
--idp-info-bg: #eff6ff;
--idp-info-border: #bfdbfe;
--idp-chart-1: #0050b9;
--idp-chart-2: #16a34a;
--idp-chart-3: #dc2626;
--idp-chart-4: #b45309;
--idp-chart-5: #6e5be6;
--idp-spark-up: #16a34a;
--idp-spark-down: #dc2626;
--idp-spark-info: #0050b9;
--idp-radius: 8px;
--idp-radius-lg: 12px;
--idp-font: 'Geist', ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--idp-display: 'Plus Jakarta Sans', 'Geist', ui-sans-serif, system-ui, sans-serif;
--idp-mono: 'Intel One Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
--idp-serif: 'Instrument Serif', Georgia, serif;
font-family: var(--idp-font);
font-feature-settings: "cv11", "ss03";
font-variant-numeric: tabular-nums;
-webkit-font-smoothing: antialiased;
}
:host([theme="dark"]),
:host([dark]) {
--idp-bg: #0a0a0a;
--idp-bg-2: #111111;
--idp-fg: #fafafa;
--idp-fg-2: #d4d4d8;
--idp-fg-3: hsl(0 0% 70%);
--idp-muted: #161616;
--idp-muted-fg: hsl(0 0% 55%);
--idp-border: #262626;
--idp-border-soft: #1c1c1c;
--idp-border-strong: #333333;
--idp-card: #121212;
--idp-card-2: #161616;
--idp-primary: #fafafa;
--idp-primary-fg: #18181b;
--idp-accent: #3b82f6;
--idp-accent-hover: #60a5fa;
--idp-accent-soft: rgba(59, 130, 246, 0.15);
--idp-destructive: #ef4444;
--idp-ok: #4ade80;
--idp-ok-bg: rgba(20, 83, 45, 0.4);
--idp-ok-border: rgba(74, 222, 128, 0.25);
--idp-warn: #fbbf24;
--idp-warn-bg: rgba(69, 26, 3, 0.6);
--idp-warn-border: rgba(251, 191, 36, 0.25);
--idp-error: #f87171;
--idp-error-bg: rgba(69, 10, 10, 0.6);
--idp-error-border: rgba(248, 113, 113, 0.25);
--idp-info: #93bbfd;
--idp-info-bg: rgba(59, 130, 246, 0.15);
--idp-info-border: rgba(59, 130, 246, 0.3);
--idp-chart-1: #3b82f6;
--idp-chart-2: #4ade80;
--idp-chart-3: #f87171;
--idp-chart-4: #fbbf24;
--idp-chart-5: #a78bfa;
--idp-spark-up: #4ade80;
--idp-spark-down: #f87171;
--idp-spark-info: #93bbfd;
}
`;
export const idpElementStyles = [cssManager.defaultStyles, idpBaseStyles];
+1
View File
@@ -0,0 +1 @@
export * from './elements/index.js';
+26
View File
@@ -0,0 +1,26 @@
import { html } from '@design.estate/dees-element';
import '../elements/index.js';
export const LandingPage = () => html`
<idp-landing-page></idp-landing-page>
`;
export const AdminConsole = () => html`
<div style="box-sizing: border-box; min-height: 100vh; padding: 32px; background: #f4f4f2; font-family: Geist, system-ui, sans-serif;">
<idp-admin-shell></idp-admin-shell>
</div>
`;
export const MobileShowcase = () => html`
<idp-mobile-showcase></idp-mobile-showcase>
`;
export const ComposedViews = () => html`
<div style="display: grid; gap: 48px; background: #0a0a0a;">
<idp-landing-page></idp-landing-page>
<div style="padding: 48px; background: #f4f4f2;">
<idp-admin-shell></idp-admin-shell>
</div>
<idp-mobile-showcase></idp-mobile-showcase>
</div>
`;
+13
View File
@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"types": ["node"]
},
"exclude": [
"dist_*/**/*.d.ts"
]
}