made tiny updates

This commit is contained in:
SinachPat
2026-05-01 09:54:14 +01:00
parent 84ddd2e248
commit 548cd11719
9 changed files with 2072 additions and 19 deletions
+832
View File
@@ -0,0 +1,832 @@
/* ════════════════════════════════════════════════════════════
MARKETING PAGE — design tokens, components, sections
Imported by app/page.tsx (marketing route).
All CSS variables here use the --bg/--fg namespace which is
separate from the --page-bg/--page-text namespace used by
the shell pages. Both namespaces respond to [data-theme="dark"]
set by providers.tsx on <html>.
════════════════════════════════════════════════════════════ */
/* ── Design tokens ─────────────────────────────────────────── */
:root {
--bg: #FFFFFF;
--bg-subtle: #FAFAFA;
--bg-muted: #F4F4F5;
--bg-strong: #EBEBEB;
--bg-inv: #0A0A0A;
--bg-inv-2: #141414;
--bg-inv-3: #1C1C1C;
--fg: #0A0A0A;
--fg-2: #52525B;
--fg-3: #A1A1AA;
--fg-4: #D4D4D8;
--fg-inv: rgba(255,255,255,0.92);
--fg-inv-2: rgba(255,255,255,0.52);
--fg-inv-3: rgba(255,255,255,0.28);
--blue: #0066FF;
--blue-dark: #0050CC;
--blue-light: #3385FF;
--blue-soft: rgba(0,102,255,0.07);
--blue-mid: rgba(0,102,255,0.15);
--purple: #7C3AED;
--teal: #0891B2;
--border: rgba(0,0,0,0.07);
--border-2: rgba(0,0,0,0.12);
--border-inv: rgba(255,255,255,0.08);
--border-inv-2:rgba(255,255,255,0.14);
--r-1: 4px;
--r-2: 8px;
--r-3: 12px;
--r-4: 16px;
--r-5: 20px;
--r-6: 24px;
--r-full: 9999px;
--sh-xs: 0 1px 2px rgba(0,0,0,0.05);
--sh-sm: 0 1px 3px rgba(0,0,0,0.07), 0 1px 2px rgba(0,0,0,0.05);
--sh-md: 0 4px 8px rgba(0,0,0,0.06), 0 2px 4px rgba(0,0,0,0.04);
--sh-lg: 0 10px 24px rgba(0,0,0,0.08), 0 4px 8px rgba(0,0,0,0.05);
--sh-xl: 0 20px 40px rgba(0,0,0,0.09), 0 8px 16px rgba(0,0,0,0.05);
--sh-2xl:0 32px 64px rgba(0,0,0,0.11), 0 16px 32px rgba(0,0,0,0.06);
--sh-product: 0 48px 96px rgba(0,0,0,0.18), 0 0 0 1px rgba(0,0,0,0.05);
--font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--mono: 'JetBrains Mono', 'SF Mono', ui-monospace, 'Cascadia Code', monospace;
}
/* ── Dark mode token overrides ─────────────────────────────── */
[data-theme="dark"] {
--bg: #0A0A0A;
--bg-subtle: #0F0F11;
--bg-muted: #161618;
--bg-strong: #1C1C1E;
--bg-inv: #F4F4F5;
--bg-inv-2: #E4E4E7;
--bg-inv-3: #D4D4D8;
--fg: rgba(255,255,255,0.92);
--fg-2: rgba(255,255,255,0.58);
--fg-3: rgba(255,255,255,0.34);
--fg-4: rgba(255,255,255,0.16);
--fg-inv: #09090B;
--fg-inv-2: rgba(0,0,0,0.62);
--fg-inv-3: rgba(0,0,0,0.35);
--border: rgba(255,255,255,0.07);
--border-2: rgba(255,255,255,0.12);
--border-inv: rgba(0,0,0,0.08);
--border-inv-2:rgba(0,0,0,0.14);
--sh-xs: 0 1px 2px rgba(0,0,0,0.3);
--sh-sm: 0 1px 3px rgba(0,0,0,0.35), 0 1px 2px rgba(0,0,0,0.25);
--sh-md: 0 4px 8px rgba(0,0,0,0.32), 0 2px 4px rgba(0,0,0,0.22);
--sh-lg: 0 10px 24px rgba(0,0,0,0.38), 0 4px 8px rgba(0,0,0,0.26);
--sh-xl: 0 20px 40px rgba(0,0,0,0.42), 0 8px 16px rgba(0,0,0,0.28);
--sh-2xl:0 32px 64px rgba(0,0,0,0.48), 0 16px 32px rgba(0,0,0,0.32);
--sh-product: 0 48px 96px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.05);
}
[data-theme="dark"] #mkt-nav.scrolled {
background: rgba(10,10,10,0.88);
}
[data-theme="dark"] .mkt-nav-mobile {
background: rgba(10,10,10,0.97);
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
}
[data-theme="dark"] .mkt-nav-dropdown-menu {
background: rgba(15,15,17,0.98);
}
/* ── Marketing page body reset ─────────────────────────────── */
.mkt-root {
font-family: var(--font);
background: var(--bg);
color: var(--fg);
line-height: 1.6;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.mkt-root a { color: inherit; text-decoration: none; }
.mkt-root button { font-family: inherit; cursor: pointer; border: none; background: none; }
.mkt-root img, .mkt-root svg { display: block; max-width: 100%; }
/* ── Layout ────────────────────────────────────────────────── */
.mkt-wrap { max-width: 1120px; margin: 0 auto; padding: 0 40px; }
.mkt-wrap-wide { max-width: 1280px; margin: 0 auto; padding: 0 40px; }
.mkt-section { padding: 128px 0; }
.mkt-section-muted { background: var(--bg-subtle); }
.mkt-section-dark { background: var(--bg-inv); }
/* ── Type scale ────────────────────────────────────────────── */
.mkt-d1 {
font-size: clamp(3rem, 7.5vw, 5.5rem);
font-weight: 900;
line-height: 1.0;
letter-spacing: -0.045em;
color: var(--fg);
}
.mkt-d2 {
font-size: clamp(2rem, 4vw, 3rem);
font-weight: 800;
line-height: 1.08;
letter-spacing: -0.038em;
color: var(--fg);
}
.mkt-d3 {
font-size: clamp(1.25rem, 2.5vw, 1.625rem);
font-weight: 700;
line-height: 1.25;
letter-spacing: -0.028em;
color: var(--fg);
}
.mkt-subtitle {
font-size: clamp(1rem, 2vw, 1.1875rem);
font-weight: 400;
line-height: 1.7;
color: var(--fg-2);
}
.mkt-label {
font-family: var(--mono);
font-size: 0.6875rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--fg-3);
}
.mkt-label-blue { color: var(--blue); }
/* ── Buttons ───────────────────────────────────────────────── */
.mkt-btn {
display: inline-flex;
align-items: center;
gap: 8px;
font-family: var(--font);
font-size: 0.9375rem;
font-weight: 600;
padding: 11px 22px;
border-radius: var(--r-2);
transition: transform 0.12s cubic-bezier(.16,1,.3,1), opacity 0.12s, box-shadow 0.12s;
white-space: nowrap;
letter-spacing: -0.01em;
position: relative;
overflow: hidden;
cursor: pointer;
}
.mkt-btn::after {
content: '';
position: absolute;
inset: 0;
background: rgba(255,255,255,0);
transition: background 0.12s;
}
.mkt-btn:hover::after { background: rgba(255,255,255,0.06); }
.mkt-btn:active { transform: scale(0.98) !important; }
.mkt-btn-primary {
background: var(--fg);
color: var(--bg);
box-shadow: var(--sh-sm);
}
.mkt-btn-primary:hover { transform: translateY(-1px); box-shadow: var(--sh-md); }
.mkt-btn-secondary {
background: transparent;
color: var(--fg);
border: 1px solid var(--border-2);
}
.mkt-btn-secondary:hover { background: var(--bg-muted); transform: translateY(-1px); }
.mkt-btn-blue {
background: var(--blue);
color: #fff;
box-shadow: 0 4px 12px rgba(0,102,255,0.3);
}
.mkt-btn-blue:hover { background: var(--blue-dark); transform: translateY(-1px); box-shadow: 0 6px 18px rgba(0,102,255,0.35); }
.mkt-btn-white {
background: #fff;
color: #0A0A0A;
box-shadow: 0 4px 12px rgba(255,255,255,0.15);
}
.mkt-btn-white:hover { background: #F5F5F5; transform: translateY(-1px); box-shadow: 0 6px 20px rgba(255,255,255,0.2); }
.mkt-btn-outline-white {
background: transparent;
color: rgba(255,255,255,0.72);
border: 1px solid rgba(255,255,255,0.18);
}
.mkt-btn-outline-white:hover { background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.9); transform: translateY(-1px); }
.mkt-btn-lg { font-size: 1rem; padding: 13px 28px; }
.mkt-btn-xl { font-size: 1.0625rem; padding: 15px 34px; border-radius: var(--r-3); }
.mkt-btn-full { width: 100%; justify-content: center; }
/* ── Badge / Pills ─────────────────────────────────────────── */
.mkt-badge {
display: inline-flex;
align-items: center;
gap: 7px;
font-size: 0.8125rem;
font-weight: 500;
padding: 5px 12px;
border-radius: var(--r-full);
border: 1px solid var(--border);
background: var(--bg-subtle);
color: var(--fg-2);
letter-spacing: -0.01em;
}
.mkt-badge-new {
background: linear-gradient(135deg, rgba(124,58,237,0.08), rgba(0,102,255,0.08));
border-color: rgba(124,58,237,0.2);
color: var(--purple);
}
/* ── Section header ────────────────────────────────────────── */
.mkt-section-head { text-align: center; max-width: 640px; margin: 0 auto 72px; }
.mkt-section-head .mkt-label { display: block; margin-bottom: 16px; }
.mkt-section-head .mkt-d2 { margin-bottom: 18px; }
.mkt-section-head .mkt-subtitle { margin: 0 auto; }
/* ── Navigation ────────────────────────────────────────────── */
#mkt-nav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 200;
height: 60px;
display: flex;
align-items: center;
padding: 0 40px;
transition: background 0.2s, border-color 0.2s, box-shadow 0.2s;
}
#mkt-nav.scrolled {
background: rgba(255,255,255,0.85);
backdrop-filter: blur(20px) saturate(1.6);
-webkit-backdrop-filter: blur(20px) saturate(1.6);
border-bottom: 1px solid var(--border);
box-shadow: var(--sh-xs);
}
.mkt-nav-inner {
max-width: 1120px;
margin: 0 auto;
width: 100%;
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
}
.mkt-nav-logo { justify-self: start; }
.mkt-nav-links { justify-self: center; }
.mkt-nav-actions{ justify-self: end; }
.mkt-nav-logo {
font-size: 1rem;
font-weight: 800;
letter-spacing: -0.04em;
color: var(--fg);
display: flex;
align-items: center;
gap: 2px;
}
.mkt-nav-logo span { color: var(--blue); }
.mkt-nav-links {
display: flex;
align-items: center;
gap: 0;
list-style: none;
}
.mkt-nav-links a {
font-size: 0.9375rem;
font-weight: 500;
color: var(--fg-2);
padding: 6px 14px;
border-radius: var(--r-1);
transition: color 0.12s, background 0.12s;
letter-spacing: -0.01em;
display: inline-flex;
align-items: center;
gap: 4px;
}
.mkt-nav-links a:hover { color: var(--fg); }
.mkt-nav-actions { display: flex; align-items: center; gap: 8px; }
.mkt-nav-theme-btn {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: var(--r-2);
background: none;
border: 1px solid var(--border-2);
color: var(--fg-3);
font-size: 0.9rem;
transition: background 0.12s, color 0.12s, border-color 0.12s;
cursor: pointer;
flex-shrink: 0;
}
.mkt-nav-theme-btn:hover { background: var(--bg-muted); color: var(--fg); border-color: var(--border-2); }
/* Hamburger */
.mkt-nav-burger {
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 5px;
width: 36px;
height: 36px;
background: none;
border: none;
cursor: pointer;
padding: 4px;
border-radius: var(--r-2);
transition: background 0.12s;
flex-shrink: 0;
}
.mkt-nav-burger:hover { background: var(--bg-muted); }
.mkt-nav-burger span {
display: block;
width: 20px;
height: 2px;
background: var(--fg);
border-radius: 2px;
transition: transform 0.22s cubic-bezier(0.16,1,0.3,1), opacity 0.18s;
transform-origin: center;
}
.mkt-nav-burger.open span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.mkt-nav-burger.open span:nth-child(2) { opacity: 0; transform: scaleX(0); }
.mkt-nav-burger.open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
/* Mobile drawer */
.mkt-nav-mobile {
display: none;
position: fixed;
top: 60px; left: 0; right: 0;
z-index: 199;
background: rgba(255,255,255,0.97);
backdrop-filter: blur(20px) saturate(1.6);
-webkit-backdrop-filter: blur(20px) saturate(1.6);
border-bottom: 1px solid var(--border);
box-shadow: 0 8px 32px rgba(0,0,0,0.07);
padding: 12px 24px 24px;
flex-direction: column;
gap: 2px;
max-height: calc(100dvh - 60px);
overflow-y: auto;
}
.mkt-nav-mobile.open { display: flex; }
.mkt-nav-mobile ul { list-style: none; display: flex; flex-direction: column; }
.mkt-nav-mobile ul li a {
display: block;
font-size: 1rem;
font-weight: 500;
color: var(--fg-2);
padding: 11px 4px;
border-bottom: 1px solid var(--border);
letter-spacing: -0.01em;
transition: color 0.1s;
}
.mkt-nav-mobile ul li:last-child a { border-bottom: none; }
.mkt-nav-mobile ul li a:hover { color: var(--fg); }
.mkt-nav-mobile-ctas {
display: flex;
flex-direction: row;
gap: 8px;
margin-top: 16px;
}
.mkt-nav-mobile-ctas .mkt-btn {
flex: 1;
justify-content: center;
padding: 10px 12px;
font-size: .875rem;
}
/* Nav dropdown */
.mkt-nav-dropdown { position: relative; }
.mkt-nav-dd-caret { transition: transform 0.15s; }
.mkt-nav-dropdown.open .mkt-nav-dd-caret { transform: rotate(180deg); }
.mkt-nav-dropdown-menu {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%) translateY(-4px);
background: rgba(255,255,255,0.98);
backdrop-filter: blur(20px);
border: 1px solid var(--border);
border-radius: var(--r-3);
box-shadow: var(--sh-xl);
padding: 10px 8px 8px;
min-width: 248px;
opacity: 0;
visibility: hidden;
transition: opacity 0.15s, transform 0.15s, visibility 0.15s;
pointer-events: none;
}
.mkt-nav-dropdown-menu.open {
opacity: 1;
visibility: visible;
transform: translateX(-50%) translateY(0);
pointer-events: all;
}
.mkt-nav-mobile-sub { display: none; padding: 2px 0 6px 14px; flex-direction: column; }
.mkt-nav-mobile-sub.open { display: flex; }
.mkt-nav-mobile-sub a { font-size: 0.875rem; color: var(--fg-3); padding: 8px 4px; border-bottom: 1px solid var(--border); letter-spacing: -0.01em; transition: color 0.1s; }
.mkt-nav-mobile-sub a:last-child { border-bottom: none; }
.mkt-nav-mobile-sub a:hover { color: var(--fg); }
.mkt-nav-dd-item {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 9px 10px;
border-radius: var(--r-2);
transition: background 0.1s;
cursor: pointer;
}
.mkt-nav-dd-item:hover { background: var(--bg-muted); }
.mkt-nav-dd-icon {
width: 30px; height: 30px;
background: var(--blue-soft);
border: 1px solid var(--blue-mid);
border-radius: 6px;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
color: var(--blue);
}
.mkt-nav-dd-text { display: flex; flex-direction: column; }
.mkt-nav-dd-name { font-size: 0.8125rem; font-weight: 600; color: var(--fg); letter-spacing: -0.01em; }
.mkt-nav-dd-desc { font-size: 0.6875rem; color: var(--fg-3); margin-top: 1px; line-height: 1.4; }
.mkt-nav-dd-sep { height: 1px; background: var(--border); margin: 4px 0; }
/* ── Hero ───────────────────────────────────────────────────── */
#mkt-hero {
padding: 168px 40px 0;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
overflow: hidden;
}
.mkt-hero-noise {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 80% 50% at 50% -10%, rgba(0,102,255,0.045) 0%, transparent 60%),
radial-gradient(ellipse 40% 30% at 80% 20%, rgba(124,58,237,0.03) 0%, transparent 50%);
pointer-events: none;
}
.mkt-hero-badge { margin-bottom: 32px; }
.mkt-hero-hl { max-width: 920px; margin-bottom: 24px; }
.mkt-hero-sub { max-width: 520px; margin: 0 auto 48px; }
.mkt-hero-ctas { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-bottom: 88px; }
.mkt-hero-note {
font-size: 0.8125rem;
color: var(--fg-3);
margin-top: -72px;
margin-bottom: 56px;
letter-spacing: -0.01em;
}
/* ── Product shell ──────────────────────────────────────────── */
.mkt-product-shell {
width: 100%;
max-width: 1080px;
border-radius: 16px 16px 0 0;
overflow: hidden;
box-shadow: var(--sh-product);
border: 1px solid rgba(255,255,255,0.06);
border-bottom: none;
position: relative;
}
.mkt-p-chrome {
background: #1C1C22;
height: 44px;
display: flex;
align-items: center;
padding: 0 16px;
gap: 8px;
border-bottom: 1px solid rgba(255,255,255,0.05);
flex-shrink: 0;
}
.mkt-p-dot { width: 12px; height: 12px; border-radius: 50%; }
.mkt-p-dot-r { background: #FF5F57; }
.mkt-p-dot-y { background: #FFBD2E; }
.mkt-p-dot-g { background: #28C840; }
.mkt-p-url {
flex: 1;
margin: 0 12px;
background: rgba(255,255,255,0.06);
border-radius: 6px;
height: 28px;
display: flex;
align-items: center;
padding: 0 12px;
font-family: var(--mono);
font-size: 0.6875rem;
color: rgba(255,255,255,0.35);
max-width: 360px;
}
.mkt-p-lock { font-size: 0.625rem; margin-right: 6px; color: rgba(255,255,255,0.25); }
.mkt-p-app {
display: grid;
grid-template-rows: 44px 1fr;
grid-template-columns: 220px 1fr 284px;
height: 560px;
background: #0D0D11;
}
.mkt-p-toolbar {
grid-column: 1 / -1;
grid-row: 1;
background: #141418;
border-bottom: 1px solid rgba(255,255,255,0.055);
display: flex;
align-items: center;
padding: 0 14px;
gap: 6px;
}
.mkt-p-wordmark { font-size: 0.875rem; font-weight: 800; letter-spacing: -0.04em; color: rgba(255,255,255,0.9); padding: 0 10px; margin-right: 4px; }
.mkt-p-wordmark span { color: #3385FF; }
.mkt-p-tb-sep { width: 1px; height: 20px; background: rgba(255,255,255,0.08); margin: 0 4px; }
.mkt-p-tb-group { display: flex; align-items: center; gap: 2px; }
.mkt-p-tb-btn { width: 30px; height: 30px; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-size: 0.8125rem; color: rgba(255,255,255,0.38); cursor: default; }
.mkt-p-tb-btn-active { background: rgba(51,133,255,0.15); color: #3385FF; }
.mkt-p-tb-btn-on { background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.7); }
.mkt-p-zoom { font-family: var(--mono); font-size: 0.6875rem; color: rgba(255,255,255,0.4); background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); border-radius: 4px; padding: 3px 8px; cursor: default; min-width: 44px; text-align: center; }
.mkt-p-live { margin-left: auto; display: flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 6px; background: rgba(16,185,129,0.1); border: 1px solid rgba(16,185,129,0.2); }
.mkt-p-live-dot { width: 6px; height: 6px; border-radius: 50%; background: #10B981; box-shadow: 0 0 0 2px rgba(16,185,129,0.25); animation: mkt-pulse-dot 2s ease-in-out infinite; }
@keyframes mkt-pulse-dot {
0%, 100% { box-shadow: 0 0 0 2px rgba(16,185,129,0.25); }
50% { box-shadow: 0 0 0 4px rgba(16,185,129,0.1); }
}
.mkt-p-live-txt { font-family: var(--mono); font-size: 0.625rem; color: #10B981; font-weight: 500; }
.mkt-p-nav { background: #111115; border-right: 1px solid rgba(255,255,255,0.055); display: flex; flex-direction: column; overflow: hidden; }
.mkt-p-nav-section { padding: 14px 12px 4px; font-family: var(--mono); font-size: 0.5625rem; color: rgba(255,255,255,0.25); letter-spacing: 0.1em; text-transform: uppercase; }
.mkt-p-nav-item { display: flex; align-items: center; gap: 8px; padding: 7px 12px; font-size: 0.75rem; color: rgba(255,255,255,0.42); cursor: default; }
.mkt-p-nav-item-sel { background: rgba(51,133,255,0.12); color: rgba(255,255,255,0.88); }
.mkt-p-nav-dot { width: 5px; height: 5px; border-radius: 50%; background: rgba(255,255,255,0.2); flex-shrink: 0; }
.mkt-p-nav-dot-sel { background: #3385FF; }
.mkt-p-sep-line { height: 1px; background: rgba(255,255,255,0.04); margin: 6px 0; }
.mkt-p-canvas { position: relative; overflow: hidden; background: #0D0D11; display: flex; align-items: center; justify-content: center; }
.mkt-p-grid { position: absolute; inset: 0; background-image: radial-gradient(circle, rgba(255,255,255,0.06) 1px, transparent 1px); background-size: 24px 24px; pointer-events: none; }
.mkt-p-artboard { position: absolute; background: #FFFFFF; border-radius: 8px; box-shadow: 0 8px 40px rgba(0,0,0,0.6); }
.mkt-p-ab-label { position: absolute; top: -22px; left: 0; font-family: var(--mono); font-size: 0.5625rem; color: rgba(255,255,255,0.4); white-space: nowrap; letter-spacing: 0.01em; }
.mkt-p-artboard-sel { box-shadow: 0 0 0 2px #3385FF, 0 8px 40px rgba(0,0,0,0.6); }
.mkt-p-artboard-sel .mkt-p-ab-label { color: #3385FF; }
.mkt-p-handle { position: absolute; width: 8px; height: 8px; background: #fff; border: 2px solid #3385FF; border-radius: 2px; z-index: 10; }
.mkt-p-handle-tl { top: -4px; left: -4px; }
.mkt-p-handle-tr { top: -4px; right: -4px; }
.mkt-p-handle-bl { bottom: -4px; left: -4px; }
.mkt-p-handle-br { bottom: -4px; right: -4px; }
.mkt-p-dash-card { padding: 18px; }
.mkt-p-dc-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.mkt-p-dc-title { font-size: 0.6875rem; font-weight: 600; color: #111; letter-spacing: -0.01em; }
.mkt-p-dc-badge { font-size: 0.5625rem; background: #ECFDF5; color: #059669; padding: 2px 7px; border-radius: 99px; font-weight: 600; font-family: monospace; }
.mkt-p-dc-val { font-size: 1.375rem; font-weight: 800; color: #0A0A0A; letter-spacing: -0.045em; line-height: 1; margin-bottom: 5px; }
.mkt-p-dc-delta { font-size: 0.625rem; color: #059669; font-weight: 600; margin-bottom: 14px; }
.mkt-p-dc-bar { height: 3px; background: #F0F0F0; border-radius: 99px; overflow: hidden; margin-bottom: 14px; }
.mkt-p-dc-fill { height: 100%; width: 68%; background: linear-gradient(90deg, #0066FF, #3385FF); border-radius: 99px; }
.mkt-p-dc-chips { display: flex; gap: 4px; }
.mkt-p-chip { font-size: 0.5rem; background: #F5F5F5; color: #666; padding: 3px 8px; border-radius: 99px; font-weight: 500; }
.mkt-p-profile { padding: 20px; display: flex; flex-direction: column; align-items: center; text-align: center; }
.mkt-p-avatar { width: 44px; height: 44px; border-radius: 50%; background: linear-gradient(135deg, #7C3AED, #0066FF); margin: 0 auto 12px; }
.mkt-p-pname { font-size: 0.75rem; font-weight: 700; color: #0A0A0A; letter-spacing: -0.02em; margin-bottom: 3px; }
.mkt-p-prole { font-size: 0.5875rem; color: #A1A1AA; margin-bottom: 14px; }
.mkt-p-prow { display: flex; justify-content: space-between; width: 100%; margin-bottom: 5px; }
.mkt-p-pk { font-size: 0.5875rem; color: #A1A1AA; }
.mkt-p-pv { font-size: 0.5875rem; font-weight: 600; color: #0A0A0A; }
.mkt-p-insp { background: #111115; border-left: 1px solid rgba(255,255,255,0.055); display: flex; flex-direction: column; overflow: hidden; }
.mkt-p-tabs { display: flex; border-bottom: 1px solid rgba(255,255,255,0.055); }
.mkt-p-tab { flex: 1; padding: 11px 0; font-family: var(--mono); font-size: 0.625rem; color: rgba(255,255,255,0.3); text-align: center; letter-spacing: 0.06em; cursor: default; text-transform: uppercase; }
.mkt-p-tab-on { color: rgba(255,255,255,0.88); border-bottom: 2px solid #3385FF; }
.mkt-p-isec { padding: 12px 14px; border-bottom: 1px solid rgba(255,255,255,0.04); }
.mkt-p-isec-hd { font-family: var(--mono); font-size: 0.5rem; color: rgba(255,255,255,0.25); letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 10px; }
.mkt-p-irow { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 8px; }
.mkt-p-ik { font-family: var(--mono); font-size: 0.625rem; color: rgba(255,255,255,0.35); }
.mkt-p-iv { font-family: var(--mono); font-size: 0.625rem; }
.mkt-p-iv-s { color: #7DD3A8; }
.mkt-p-iv-n { color: #7EB8FF; }
.mkt-p-iv-b { color: #FFBA7B; }
.mkt-p-diff { font-family: var(--mono); font-size: 0.6rem; padding: 3px 8px; border-radius: 4px; margin-bottom: 3px; line-height: 1.5; }
.mkt-p-diff-del { background: rgba(255,70,70,0.09); color: #FF8080; }
.mkt-p-diff-add { background: rgba(70,220,120,0.09); color: #7DDBA0; }
.mkt-p-statusbar { margin-top: auto; height: 26px; background: #0D0D11; border-top: 1px solid rgba(255,255,255,0.04); display: flex; align-items: center; padding: 0 14px; gap: 14px; flex-shrink: 0; }
.mkt-p-st { font-family: var(--mono); font-size: 0.5625rem; color: rgba(255,255,255,0.25); }
.mkt-p-st-on { color: rgba(255,255,255,0.55); }
/* ── Logos strip ────────────────────────────────────────────── */
#mkt-logos { padding: 56px 0 80px; border-bottom: 1px solid var(--border); }
.mkt-logos-label { text-align: center; margin-bottom: 32px; }
.mkt-logos-list { display: flex; align-items: center; justify-content: center; gap: 56px; flex-wrap: wrap; }
.mkt-logo-item { font-size: 1rem; font-weight: 700; color: var(--fg-4); letter-spacing: -0.03em; transition: color 0.15s; cursor: default; }
.mkt-logo-item:hover { color: var(--fg-3); }
/* ── Feature blocks ─────────────────────────────────────────── */
.mkt-feat-block { padding: 120px 40px; overflow: hidden; }
.mkt-feat-block-inner { max-width: 1120px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 96px; align-items: center; }
.mkt-feat-block-inner-reverse { direction: rtl; }
.mkt-feat-block-inner-reverse > * { direction: ltr; }
.mkt-feat-copy .mkt-label { display: block; margin-bottom: 20px; }
.mkt-feat-copy .mkt-d3 { margin-bottom: 18px; }
.mkt-feat-copy .mkt-subtitle { margin-bottom: 28px; }
.mkt-feat-points { list-style: none; display: flex; flex-direction: column; gap: 12px; margin-bottom: 32px; }
.mkt-feat-points li { display: flex; align-items: flex-start; gap: 12px; font-size: 0.9375rem; color: var(--fg-2); line-height: 1.55; }
.mkt-feat-point-icon { width: 20px; height: 20px; border-radius: var(--r-1); background: var(--blue-soft); display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 1px; font-size: 0.625rem; color: var(--blue); }
.mkt-feat-visual { border-radius: var(--r-5); overflow: hidden; box-shadow: var(--sh-2xl); border: 1px solid var(--border); aspect-ratio: 4/3; position: relative; }
.mkt-feat-visual-dark { background: #0D0D11; border-color: rgba(255,255,255,0.07); }
.mkt-fv-artboard-scene { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.mkt-fv-grid { position: absolute; inset: 0; background-image: radial-gradient(circle, rgba(255,255,255,0.06) 1px, transparent 1px); background-size: 22px 22px; }
.mkt-fv-ab { width: 200px; background: #fff; border-radius: 8px; padding: 16px; box-shadow: 0 8px 40px rgba(0,0,0,0.55); position: relative; z-index: 1; outline: 2px solid #3385FF; outline-offset: 2px; }
.mkt-fv-ab-lbl { position: absolute; top: -20px; left: 0; font-family: var(--mono); font-size: 0.5rem; color: #3385FF; white-space: nowrap; }
.mkt-fv-mini-title { font-size: 0.625rem; font-weight: 700; color: #111; margin-bottom: 8px; }
.mkt-fv-mini-val { font-size: 1rem; font-weight: 800; color: #0A0A0A; letter-spacing: -0.04em; margin-bottom: 6px; }
.mkt-fv-mini-bar { height: 2px; background: #F0F0F0; border-radius: 99px; overflow: hidden; }
.mkt-fv-mini-fill { height: 100%; width: 68%; background: #0066FF; }
.mkt-fv-diff-scene { padding: 24px; display: flex; flex-direction: column; gap: 6px; }
.mkt-fv-diff-header { font-family: var(--mono); font-size: 0.5625rem; color: rgba(255,255,255,0.3); letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 8px; }
.mkt-fv-diff-file { font-family: var(--mono); font-size: 0.625rem; color: rgba(255,255,255,0.5); margin-bottom: 12px; }
.mkt-fv-line { font-family: var(--mono); font-size: 0.65rem; padding: 4px 8px; border-radius: 4px; line-height: 1.5; }
.mkt-fv-line-ctx { color: rgba(255,255,255,0.3); }
.mkt-fv-line-del { background: rgba(255,70,70,0.1); color: #FF8080; }
.mkt-fv-line-add { background: rgba(70,220,120,0.1); color: #7DDBA0; }
.mkt-fv-graph-scene { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.mkt-fv-node { position: absolute; background: #1C1C26; border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; padding: 7px 12px; font-family: var(--mono); font-size: 0.5875rem; color: rgba(255,255,255,0.7); white-space: nowrap; box-shadow: 0 4px 12px rgba(0,0,0,0.4); }
.mkt-fv-node-root { background: rgba(51,133,255,0.15); border-color: rgba(51,133,255,0.3); color: #7EB8FF; }
/* ── Features grid ──────────────────────────────────────────── */
#mkt-features { padding: 128px 0; background: var(--bg-subtle); }
.mkt-feat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--border); border-radius: var(--r-5); overflow: hidden; }
.mkt-feat-cell { background: var(--bg-subtle); padding: 40px 36px; transition: background 0.15s; position: relative; }
.mkt-feat-cell:hover { background: var(--bg); }
.mkt-feat-cell::after { content: ''; position: absolute; inset: 0; opacity: 0; background: radial-gradient(ellipse 80% 60% at var(--mx,50%) var(--my,50%), rgba(0,102,255,0.04), transparent); transition: opacity 0.3s; pointer-events: none; }
.mkt-feat-cell:hover::after { opacity: 1; }
.mkt-feat-icon { width: 40px; height: 40px; border-radius: var(--r-2); background: var(--blue-soft); display: flex; align-items: center; justify-content: center; font-size: 1.125rem; margin-bottom: 20px; border: 1px solid var(--blue-mid); }
.mkt-feat-cell h3 { font-size: 1.0625rem; font-weight: 700; color: var(--fg); letter-spacing: -0.025em; margin-bottom: 10px; line-height: 1.25; }
.mkt-feat-cell p { font-size: 0.9375rem; color: var(--fg-2); line-height: 1.65; }
/* ── Workflow ───────────────────────────────────────────────── */
#mkt-workflow { padding: 128px 0; }
.mkt-steps-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0; margin-top: 72px; position: relative; }
.mkt-steps-row::before { content: ''; position: absolute; top: 26px; left: calc(10% + 8px); right: calc(10% + 8px); height: 1px; background: linear-gradient(90deg, transparent, var(--border-2) 15%, var(--border-2) 85%, transparent); z-index: 0; }
.mkt-step { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 0 16px; position: relative; z-index: 1; }
.mkt-step-num { width: 52px; height: 52px; border-radius: 50%; background: var(--bg); border: 1px solid var(--border-2); display: flex; align-items: center; justify-content: center; font-size: 1rem; font-weight: 800; color: var(--blue); letter-spacing: -0.04em; margin-bottom: 20px; box-shadow: var(--sh-sm); }
.mkt-step h3 { font-size: 0.9375rem; font-weight: 700; color: var(--fg); letter-spacing: -0.02em; margin-bottom: 8px; }
.mkt-step p { font-size: 0.8125rem; color: var(--fg-2); line-height: 1.6; }
/* ── Integrations ───────────────────────────────────────────── */
#mkt-integrations { background: #08080C; padding: 140px 0; position: relative; overflow: hidden; }
#mkt-integrations::before { content: ''; position: absolute; inset: 0; background-image: radial-gradient(circle, rgba(255,255,255,0.032) 1px, transparent 1px); background-size: 28px 28px; pointer-events: none; }
#mkt-integrations .mkt-section-head .mkt-label { color: rgba(255,255,255,0.28); }
#mkt-integrations .mkt-section-head .mkt-d2 { color: #fff; }
#mkt-integrations .mkt-section-head .mkt-subtitle { color: rgba(255,255,255,0.42); }
.mkt-int-orb { position: absolute; border-radius: 50%; pointer-events: none; filter: blur(80px); opacity: 0.55; }
.mkt-int-orb-1 { width:500px;height:300px;top:0;left:50%;transform:translateX(-50%); background:radial-gradient(ellipse,rgba(51,133,255,.15),transparent 70%); }
.mkt-int-orb-2 { width:320px;height:320px;bottom:10%;left:5%; background:radial-gradient(ellipse,rgba(162,89,255,.1),transparent 70%); }
.mkt-int-orb-3 { width:260px;height:260px;bottom:15%;right:8%; background:radial-gradient(ellipse,rgba(97,218,251,.08),transparent 70%); }
.mkt-int-bento { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 64px; position: relative; z-index: 1; }
.mkt-int-card { background: #111115; border: 1px solid rgba(255,255,255,0.07); border-radius: 16px; padding: 26px 28px; position: relative; overflow: hidden; display: flex; flex-direction: column; gap: 14px; min-height: 168px; transition: border-color .2s, box-shadow .25s cubic-bezier(.16,1,.3,1), transform .2s cubic-bezier(.16,1,.3,1); cursor: default; }
.mkt-int-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px; background: linear-gradient(90deg, transparent, var(--glow,rgba(255,255,255,.3)) 40%, transparent); opacity: .65; }
.mkt-int-card::after { content: ''; position: absolute; inset: 0; opacity: 0; background: radial-gradient(ellipse 70% 60% at var(--mx,50%) var(--my,50%), var(--glow-soft,rgba(255,255,255,.04)), transparent); transition: opacity .3s; pointer-events: none; }
.mkt-int-card:hover::after { opacity: 1; }
.mkt-int-card:hover { border-color: rgba(255,255,255,.13); box-shadow: 0 0 0 1px var(--glow-dim,rgba(255,255,255,.06)), 0 20px 48px rgba(0,0,0,.5), 0 0 56px var(--glow-ambient,rgba(255,255,255,.02)); transform: translateY(-3px); }
.mkt-int-card-w2 { grid-column: span 2; }
.mkt-int-ico { width: 42px; height: 42px; border-radius: 11px; background: var(--ico-bg,rgba(255,255,255,.07)); border: 1px solid var(--ico-bd,rgba(255,255,255,.1)); display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; z-index: 1; }
.mkt-int-name { font-size: .9375rem; font-weight: 700; color: rgba(255,255,255,.9); letter-spacing: -.025em; line-height: 1.25; position: relative; z-index: 1; }
.mkt-int-desc { font-size: .8rem; color: rgba(255,255,255,.36); line-height: 1.6; flex: 1; position: relative; z-index: 1; }
.mkt-int-footer { display: flex; align-items: center; justify-content: space-between; padding-top: 12px; border-top: 1px solid rgba(255,255,255,.05); position: relative; z-index: 1; margin-top: auto; }
.mkt-int-tag { font-family: 'JetBrains Mono',ui-monospace,monospace; font-size: .5rem; font-weight: 500; letter-spacing: .09em; text-transform: uppercase; color: rgba(255,255,255,.2); }
.mkt-int-status { display: flex; align-items: center; gap: 5px; font-family: 'JetBrains Mono',ui-monospace,monospace; font-size: .5rem; font-weight: 500; letter-spacing: .05em; color: rgba(255,255,255,.28); }
.mkt-int-dot { width: 5px; height: 5px; border-radius: 50%; background: #10B981; box-shadow: 0 0 6px rgba(16,185,129,.7); animation: mkt-int-pulse 2.4s ease-in-out infinite; }
@keyframes mkt-int-pulse {
0%,100% { opacity:1; box-shadow:0 0 6px rgba(16,185,129,.7); }
50% { opacity:.4; box-shadow:0 0 2px rgba(16,185,129,.3); }
}
.mkt-int-more { background: transparent !important; border-style: dashed !important; border-color: rgba(255,255,255,.08) !important; align-items: center; justify-content: center; text-align: center; gap: 6px !important; min-height: 168px; }
.mkt-int-more::before, .mkt-int-more::after { display: none; }
.mkt-int-more-n { font-size: 2rem; font-weight: 900; color: rgba(255,255,255,.1); letter-spacing: -.05em; line-height: 1; }
.mkt-int-more-l { font-size: .75rem; color: rgba(255,255,255,.22); font-weight: 500; letter-spacing: -.01em; }
/* ── Comparison ─────────────────────────────────────────────── */
#mkt-comparison { padding: 128px 0; }
.mkt-cmp-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 72px; }
.mkt-cmp-card { border-radius: var(--r-5); padding: 40px 32px; border: 1px solid var(--border); background: var(--bg); transition: box-shadow 0.2s; }
.mkt-cmp-card:hover { box-shadow: var(--sh-lg); }
.mkt-cmp-card-hero { background: #06060E; border-color: rgba(51,133,255,0.4); box-shadow: 0 0 0 1px rgba(51,133,255,0.18), var(--sh-xl); transform: translateY(-10px); }
.mkt-cmp-eyebrow { font-family: var(--mono); font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-3); margin-bottom: 14px; }
.mkt-cmp-card-hero .mkt-cmp-eyebrow { color: rgba(51,133,255,0.85); }
.mkt-cmp-name { font-size: 1.625rem; font-weight: 800; letter-spacing: -0.04em; color: var(--fg); margin-bottom: 16px; line-height: 1.15; }
.mkt-cmp-card-hero .mkt-cmp-name { color: #fff; }
.mkt-cmp-hr { height: 1px; background: var(--border); margin: 18px 0; }
.mkt-cmp-card-hero .mkt-cmp-hr { background: rgba(255,255,255,0.08); }
.mkt-cmp-body { font-size: 0.9375rem; line-height: 1.7; color: var(--fg-2); margin-bottom: 20px; }
.mkt-cmp-card-hero .mkt-cmp-body { color: rgba(255,255,255,0.62); }
.mkt-cmp-tags { display: flex; flex-wrap: wrap; gap: 7px; }
.mkt-cmp-tag { font-size: 0.8125rem; padding: 4px 12px; border-radius: var(--r-full); font-weight: 500; background: var(--bg-muted); color: var(--fg-2); }
.mkt-cmp-card-hero .mkt-cmp-tag { background: rgba(51,133,255,0.12); color: rgba(51,133,255,0.9); border: 1px solid rgba(51,133,255,0.2); }
/* ── Testimonial ────────────────────────────────────────────── */
#mkt-testimonial { padding: 160px 40px; background: var(--bg-subtle); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.mkt-testi-inner { max-width: 760px; margin: 0 auto; text-align: center; }
.mkt-testi-logo { margin: 0 auto 40px; display: flex; align-items: center; justify-content: center; gap: 10px; }
.mkt-testi-logo-dot { width: 32px; height: 32px; background: linear-gradient(135deg, var(--purple), var(--blue)); border-radius: var(--r-2); }
.mkt-testi-logo-name { font-size: 0.9375rem; font-weight: 700; color: var(--fg); letter-spacing: -0.02em; }
.mkt-testi-quote { font-size: clamp(1.25rem, 3vw, 1.875rem); font-weight: 600; line-height: 1.45; letter-spacing: -0.028em; color: var(--fg); margin-bottom: 32px; }
.mkt-testi-attr { font-size: 0.9375rem; color: var(--fg-3); letter-spacing: -0.01em; }
.mkt-testi-attr strong { color: var(--fg-2); font-weight: 500; }
/* ── Pricing ────────────────────────────────────────────────── */
#mkt-pricing { padding: 128px 0; }
.mkt-pricing-toggle { display: flex; align-items: center; justify-content: center; gap: 12px; margin-bottom: 56px; }
.mkt-toggle-pill { display: flex; background: var(--bg-muted); border-radius: var(--r-full); padding: 4px; border: 1px solid var(--border); }
.mkt-toggle-opt { padding: 7px 20px; border-radius: var(--r-full); font-size: 0.875rem; font-weight: 500; color: var(--fg-2); cursor: pointer; transition: all 0.15s; letter-spacing: -0.01em; }
.mkt-toggle-opt-active { background: var(--bg); color: var(--fg); box-shadow: var(--sh-xs); }
.mkt-toggle-save { font-size: 0.8125rem; font-weight: 600; color: #059669; background: rgba(16,185,129,0.08); border: 1px solid rgba(16,185,129,0.2); padding: 4px 12px; border-radius: var(--r-full); }
.mkt-px-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; align-items: start; }
.mkt-px-card { border-radius: var(--r-5); padding: 40px 32px; background: var(--bg); border: 1px solid var(--border); transition: box-shadow 0.2s; }
.mkt-px-card:hover { box-shadow: var(--sh-lg); }
.mkt-px-card-featured { border-color: var(--blue); box-shadow: 0 0 0 1px var(--blue), var(--sh-xl); position: relative; }
.mkt-px-tag { position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: var(--blue); color: #fff; font-size: 0.75rem; font-weight: 600; padding: 4px 14px; border-radius: var(--r-full); white-space: nowrap; }
.mkt-px-tier { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--fg-3); margin-bottom: 12px; }
.mkt-px-card-featured .mkt-px-tier { color: var(--blue); }
.mkt-px-price { font-size: 3rem; font-weight: 900; letter-spacing: -0.05em; color: var(--fg); line-height: 1; margin-bottom: 4px; }
.mkt-px-period { font-size: 0.875rem; color: var(--fg-3); margin-bottom: 28px; letter-spacing: -0.01em; }
.mkt-px-hr { height: 1px; background: var(--border); margin: 24px 0; }
.mkt-px-feats { list-style: none; display: flex; flex-direction: column; gap: 12px; margin-bottom: 32px; }
.mkt-px-feats li { display: flex; align-items: flex-start; gap: 12px; font-size: 0.9375rem; color: var(--fg-2); line-height: 1.5; letter-spacing: -0.01em; }
.mkt-px-check { color: var(--blue); font-weight: 700; flex-shrink: 0; margin-top: 1px; font-size: 0.875rem; }
/* ── Bottom CTA ─────────────────────────────────────────────── */
#mkt-cta-bottom { background: #06060E; padding: 140px 40px; position: relative; overflow: hidden; text-align: center; }
#mkt-cta-bottom::before { content: ''; position: absolute; inset: 0; background: radial-gradient(ellipse 60% 80% at 30% 50%, rgba(124,58,237,0.18) 0%, transparent 60%), radial-gradient(ellipse 60% 80% at 70% 50%, rgba(0,102,255,0.15) 0%, transparent 60%); pointer-events: none; }
.mkt-cta-inner { position: relative; max-width: 720px; margin: 0 auto; }
.mkt-cta-headline { font-size: clamp(2.5rem, 5vw, 4rem); font-weight: 900; letter-spacing: -0.045em; color: rgba(255,255,255,0.94); line-height: 1.05; margin-bottom: 20px; }
.mkt-cta-sub { font-size: 1.125rem; color: rgba(255,255,255,0.52); margin-bottom: 48px; line-height: 1.65; }
.mkt-cta-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
/* ── Footer ─────────────────────────────────────────────────── */
.mkt-footer { background: #08080C; border-top: 1px solid rgba(255,255,255,0.06); padding: 80px 40px 40px; }
.mkt-ft-inner { max-width: 1120px; margin: 0 auto; }
.mkt-ft-top { display: grid; grid-template-columns: 280px repeat(4, 1fr); gap: 48px; padding-bottom: 64px; border-bottom: 1px solid rgba(255,255,255,0.07); margin-bottom: 40px; }
.mkt-ft-brand-logo { font-size: 1.0625rem; font-weight: 800; letter-spacing: -0.04em; color: rgba(255,255,255,0.92); margin-bottom: 10px; }
.mkt-ft-brand-logo span { color: #3385FF; }
.mkt-ft-brand-tag { font-size: 0.875rem; color: rgba(255,255,255,0.32); line-height: 1.65; max-width: 200px; }
.mkt-ft-col h4 { font-size: 0.9375rem; font-weight: 600; color: rgba(255,255,255,0.75); margin-bottom: 18px; letter-spacing: -0.02em; }
.mkt-ft-col ul { list-style: none; display: flex; flex-direction: column; gap: 11px; }
.mkt-ft-col a { font-size: 0.9375rem; color: rgba(255,255,255,0.35); transition: color 0.14s; letter-spacing: -0.01em; }
.mkt-ft-col a:hover { color: rgba(255,255,255,0.7); }
.mkt-ft-bottom { display: flex; justify-content: space-between; align-items: center; }
.mkt-ft-copy { font-size: 0.875rem; color: rgba(255,255,255,0.25); letter-spacing: -0.01em; }
.mkt-theme-toggle { display: flex; align-items: center; gap: 8px; padding: 8px 16px; border-radius: var(--r-full); background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); font-size: 0.875rem; color: rgba(255,255,255,0.45); transition: background 0.14s, color 0.14s; letter-spacing: -0.01em; cursor: pointer; }
.mkt-theme-toggle:hover { background: rgba(255,255,255,0.1); color: rgba(255,255,255,0.75); }
/* ── Scroll reveal ──────────────────────────────────────────── */
.mkt-reveal { opacity: 0; transform: translateY(24px); transition: opacity 0.7s cubic-bezier(0.16,1,0.3,1), transform 0.7s cubic-bezier(0.16,1,0.3,1); }
.mkt-reveal.in { opacity: 1; transform: none; }
.mkt-stagger > * { opacity: 0; transform: translateY(18px); transition: opacity 0.55s cubic-bezier(0.16,1,0.3,1), transform 0.55s cubic-bezier(0.16,1,0.3,1); }
.mkt-stagger.in > *:nth-child(1) { opacity: 1; transform: none; transition-delay: 0.00s; }
.mkt-stagger.in > *:nth-child(2) { opacity: 1; transform: none; transition-delay: 0.07s; }
.mkt-stagger.in > *:nth-child(3) { opacity: 1; transform: none; transition-delay: 0.14s; }
.mkt-stagger.in > *:nth-child(4) { opacity: 1; transform: none; transition-delay: 0.21s; }
.mkt-stagger.in > *:nth-child(5) { opacity: 1; transform: none; transition-delay: 0.28s; }
.mkt-stagger.in > *:nth-child(6) { opacity: 1; transform: none; transition-delay: 0.35s; }
/* ── Responsive ─────────────────────────────────────────────── */
@media (max-width: 1024px) {
.mkt-feat-block-inner { grid-template-columns: 1fr; gap: 56px; }
.mkt-feat-block-inner-reverse { direction: ltr; }
.mkt-feat-grid { grid-template-columns: repeat(2, 1fr); }
.mkt-cmp-grid, .mkt-px-grid { grid-template-columns: 1fr; max-width: 480px; margin-left: auto; margin-right: auto; }
.mkt-px-card-featured { margin: 8px 0; }
.mkt-int-bento { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 768px) {
.mkt-int-bento { grid-template-columns: repeat(2, 1fr); }
.mkt-int-card-w2 { grid-column: span 2; }
.mkt-wrap, .mkt-wrap-wide { padding: 0 24px; }
#mkt-nav { padding: 0 24px; }
.mkt-nav-inner { display: flex; justify-content: space-between; align-items: center; width: 100%; }
.mkt-nav-links { display: none; }
.mkt-nav-actions .mkt-btn { display: none; }
.mkt-nav-actions .mkt-nav-theme-btn { display: none; }
.mkt-nav-burger { display: flex; }
.mkt-feat-block { padding: 80px 24px; }
#mkt-hero { padding: 140px 24px 0; }
.mkt-hero-ctas { flex-direction: column; align-items: center; }
.mkt-p-app { height: 380px; grid-template-columns: 160px 1fr; }
.mkt-p-insp { display: none; }
.mkt-feat-grid { grid-template-columns: 1fr; }
.mkt-steps-row { grid-template-columns: 1fr 1fr; gap: 40px; }
.mkt-steps-row::before { display: none; }
.mkt-ft-top { grid-template-columns: 1fr 1fr; }
.mkt-logos-list { gap: 32px; }
.mkt-cta-actions { flex-direction: column; align-items: center; }
}
@media (max-width: 480px) {
.mkt-d1 { font-size: 2.5rem; }
.mkt-ft-top { grid-template-columns: 1fr; }
.mkt-p-app { grid-template-columns: 1fr; height: 320px; }
.mkt-p-nav { display: none; }
.mkt-int-bento { grid-template-columns: 1fr; }
.mkt-int-card-w2 { grid-column: span 1; }
}
File diff suppressed because it is too large Load Diff
@@ -54,8 +54,11 @@ export function Artboard({ id, label, x, y, width, height, renderUrl, route, onR
// ── Fiber tree (from LiveArtboard) ─────────────────────────────────────────
const [localFiberRoot, setLocalFiberRoot] = useState<FiberNode | undefined>(undefined);
// Set true when READY fires but no React commit follows (static HTML page).
const [isStaticPage, setIsStaticPage] = useState(false);
const handleFiberUpdate = useCallback((root: FiberNode) => {
setIsStaticPage(false); // React confirmed — clear any static-page warning
setFiberRoot(id, root);
setArtboardLive(id, true);
setLocalFiberRoot(root);
@@ -355,11 +358,31 @@ export function Artboard({ id, label, x, y, width, height, renderUrl, route, onR
width={width}
height={height}
selectedComponentId={selectedComponentId}
onReady={() => { setArtboardLive(id, true); setIsStaticPage(false); }}
onFiberTreeUpdate={handleFiberUpdate}
onComponentSelected={handleComponentSelected}
onComponentStylesUpdate={handleComponentStylesUpdate}
onRoutesDiscovered={(routes) => onRoutesDiscovered?.(id, routes)}
onStaticPageDetected={() => setIsStaticPage(true)}
/>
{/* Static-page banner — shown when the proxy serves a non-React page */}
{isStaticPage && (
<div style={{
position: 'absolute', bottom: 0, left: 0, right: 0,
background: 'rgba(245,158,11,0.92)', backdropFilter: 'blur(8px)',
padding: '6px 12px', display: 'flex', alignItems: 'center', gap: 8,
zIndex: 20, pointerEvents: 'none',
}}>
<span style={{ fontSize: 12 }}></span>
<span style={{
fontFamily: "'JetBrains Mono', monospace", fontSize: '0.5875rem',
color: '#1C1917', letterSpacing: '-0.01em',
}}>
Static HTML page no React components detected. Navigate to a React route to enable inspection.
</span>
</div>
)}
<SelectionOverlay
artboardId={id}
{...(localFiberRoot !== undefined ? { fiberRoot: localFiberRoot } : {})}
@@ -31,6 +31,9 @@ export interface LiveArtboardProps {
onComponentStylesUpdate?: (nodeId: string, styles: Record<string, string>) => void;
/** Called when the iframe discovers routes in the running app. */
onRoutesDiscovered?: (routes: Array<{ path: string; label: string }>) => void;
/** Called when READY fires but no React commits arrive within 4 s signals a
* static HTML page where the fiber hook can't find a React runtime. */
onStaticPageDetected?: () => void;
style?: React.CSSProperties;
}
@@ -48,11 +51,16 @@ export function LiveArtboard({
onComponentSelected,
onComponentStylesUpdate,
onRoutesDiscovered,
onStaticPageDetected,
style,
}: LiveArtboardProps) {
const iframeRef = useRef<HTMLIFrameElement>(null);
// Track whether the iframe has sent READY so we don't send messages too early.
const isReadyRef = useRef(false);
// Whether a React commit (FIBER_TREE_UPDATE) has been received for this src.
const hasReactRef = useRef(false);
// Timer that fires if READY arrives but no React commits follow — static page.
const staticTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
// After READY fires the nodeMap inside the iframe is still empty — we can't
// request styles until after the first FIBER_TREE_UPDATE (which populates it).
// This ref holds the nodeId to re-request styles for after that first commit.
@@ -63,7 +71,10 @@ export function LiveArtboard({
// fire against a half-loaded iframe between navigation and the new READY.
useEffect(() => {
isReadyRef.current = false;
hasReactRef.current = false;
pendingStylesFetchRef.current = null;
if (staticTimerRef.current) clearTimeout(staticTimerRef.current);
staticTimerRef.current = null;
}, [src]);
// ── Send a typed message to the iframe ───────────────────────────────────
@@ -100,8 +111,19 @@ export function LiveArtboard({
pendingStylesFetchRef.current = selectedComponentId;
}
onReady?.();
// Start a 4-second timer: if React never commits, this is a static page.
if (staticTimerRef.current) clearTimeout(staticTimerRef.current);
staticTimerRef.current = setTimeout(() => {
if (!hasReactRef.current) onStaticPageDetected?.();
}, 4000);
break;
case 'FIBER_TREE_UPDATE':
// Mark that this iframe contains a live React app.
hasReactRef.current = true;
if (staticTimerRef.current) {
clearTimeout(staticTimerRef.current);
staticTimerRef.current = null;
}
onFiberTreeUpdate?.(msg.root);
// If READY deferred a style fetch (nodeMap was empty at that point),
// the first commit has now populated nodeMap — request styles now.
File diff suppressed because one or more lines are too long
+123
View File
@@ -0,0 +1,123 @@
// ── Universal DOM Inspector Script ───────────────────────────────────────────
// Generates a self-contained IIFE that can be injected into ANY HTML page
// (React, Vue, static HTML — anything) via the CLI proxy or the live-sdk.
//
// What it does:
// • Intercepts clicks → sends COMPONENT_SELECTED + ELEMENT_STYLES
// • Responds to PATCH_ELEMENT_STYLE / REMOVE_ELEMENT / REQUEST_ELEMENT_STYLES
// • Discovers routes from <a> links → sends ROUTES_DISCOVERED
// • Applies SET_DESIGN_TOKENS as CSS custom properties on :root
// • Sends READY once the DOM is interactive
//
// No React dependency. Works for any HTML page loaded in an Originmain iframe.
import { RENDERER_SOURCE, HOST_SOURCE } from './protocol.js';
/** Returns the injectable DOM inspector script as a string (IIFE). */
export function buildDomInspectorScript(): string {
return `(function(){
/* ── Guard ─────────────────────────────────── */
var PREFIX='om:',SRC=${JSON.stringify(RENDERER_SOURCE)},HOST=${JSON.stringify(HOST_SOURCE)};
if(window.parent===window)return;
var aid='';
try{var n=window.name;if(typeof n==='string'&&n.indexOf(PREFIX)===0)aid=n.slice(PREFIX.length);}catch(_){}
if(!aid)return;
/* ── postMessage ────────────────────────────── */
function post(m){try{window.parent.postMessage({source:SRC,artboardId:aid,message:m},'*');}catch(_){}}
/* ── Element IDs ────────────────────────────── */
var seq=0;
function assignId(el){var cur=el.getAttribute('data-om');if(cur)return cur;var id=(++seq).toString(36);try{el.setAttribute('data-om',id);}catch(_){}return id;}
function byId(id){return document.querySelector('[data-om="'+id+'"]');}
/* ── Component name (React-aware) ───────────── */
function nameOf(el){
var ks=Object.keys(el);
for(var i=0;i<ks.length;i++){
if(ks[i].indexOf('__reactFiber')!==0)continue;
var f=el[ks[i]];
while(f){var t=f.type;if(typeof t==='function'){var nm=t.displayName||t.name;if(nm&&!/^[a-z]/.test(nm))return nm;}f=f.return;}
}
var tag=(el.tagName||'el').toLowerCase();
var id2=el.id?'#'+el.id:'';
var cls='';
if(el.className&&typeof el.className==='string'){var fc=el.className.trim().split(/\\s+/)[0];if(fc)cls='.'+fc;}
return tag+(id2||cls);
}
/* ── CSS capture ────────────────────────────── */
var PROPS=['width','height','display','position','top','left','right','bottom',
'padding-top','padding-right','padding-bottom','padding-left',
'margin-top','margin-right','margin-bottom','margin-left',
'background-color','color','font-size','font-weight','font-family',
'line-height','letter-spacing','text-align','border-radius',
'border-width','border-color','border-style','box-shadow','opacity',
'flex-direction','align-items','justify-content','gap','overflow','z-index'];
function capStyles(el){var cs=window.getComputedStyle(el),out={};for(var i=0;i<PROPS.length;i++){var v=cs.getPropertyValue(PROPS[i]);if(v)out[PROPS[i]]=v;}return out;}
function capRect(el){var r=el.getBoundingClientRect();return{x:r.left,y:r.top,width:r.width,height:r.height};}
/* ── Selection highlight ────────────────────── */
var selEl=null;
function ensureCss(){
if(document.getElementById('om-css'))return;
var s=document.createElement('style');s.id='om-css';
s.textContent='[data-om-s]{outline:2px solid rgba(51,133,255,0.85)!important;outline-offset:1px!important;}';
(document.head||document.documentElement).appendChild(s);
}
function selectEl(el){if(selEl)selEl.removeAttribute('data-om-s');selEl=el;if(el)el.setAttribute('data-om-s','');}
/* ── Click handler ──────────────────────────── */
document.addEventListener('click',function(e){
e.preventDefault();e.stopPropagation();
var el=e.target;
if(!el||el===document.documentElement||el===document.body){selectEl(null);post({type:'COMPONENT_DESELECTED'});return;}
var id=assignId(el);
selectEl(el);
post({type:'COMPONENT_SELECTED',nodeId:id,nodeName:nameOf(el),rect:capRect(el)});
post({type:'ELEMENT_STYLES',nodeId:id,styles:capStyles(el)});
},true);
/* ── Host messages ──────────────────────────── */
window.addEventListener('message',function(e){
var d=e.data;
if(!d||d.source!==HOST||d.artboardId!==aid)return;
var m=d.message||{};var el;
if(m.type==='PATCH_ELEMENT_STYLE'){el=byId(m.nodeId);if(el)el.style.setProperty(m.property,m.value);}
else if(m.type==='REMOVE_ELEMENT'){el=byId(m.nodeId);if(el)el.style.display='none';}
else if(m.type==='REQUEST_ELEMENT_STYLES'){el=byId(m.nodeId);if(el)post({type:'ELEMENT_STYLES',nodeId:m.nodeId,styles:capStyles(el)});}
else if(m.type==='SELECT_COMPONENT'){el=byId(m.nodeId);selectEl(el||null);}
else if(m.type==='DESELECT'){selectEl(null);}
else if(m.type==='SET_DESIGN_TOKENS'){var tks=m.tokens||{},ks=Object.keys(tks);for(var i=0;i<ks.length;i++)document.documentElement.style.setProperty(ks[i],tks[ks[i]]);}
else if(m.type==='NAVIGATE'){if(m.path)window.location.pathname=m.path;}
});
/* ── Route discovery ────────────────────────── */
function discoverRoutes(){
var seen=Object.create(null),out=[];
var as=document.querySelectorAll('a[href]');
for(var i=0;i<as.length;i++){
try{
var url=new URL(as[i].href,window.location.href);
if(url.origin!==window.location.origin)continue;
var p=url.pathname;if(seen[p])continue;seen[p]=true;
var label=(as[i].textContent||'').trim().slice(0,40)||p;
out.push({path:p,label:label});
}catch(_){}
}
if(out.length)post({type:'ROUTES_DISCOVERED',routes:out});
}
/* ── Init ───────────────────────────────────── */
function init(){ensureCss();post({type:'READY'});discoverRoutes();}
if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',init);}else{init();}
})();`;
}
/**
* @deprecated Use buildDomInspectorScript() instead.
* Kept for backward compatibility now returns the DOM inspector, not the fiber hook.
*/
export function buildProxyFiberHookScript(): string {
return buildDomInspectorScript();
}
+53 -11
View File
@@ -478,10 +478,13 @@ export function buildProxyFiberHookScript(): string {
}
// ── Route discovery ───────────────────────────────────────────────────────
// Scans <a href> links and the fiber nodeMap for same-origin routes, then
// posts ROUTES_DISCOVERED so the host canvas can auto-create artboards for
// every page. Called once 800 ms after READY (giving React time to paint)
// and again on every SPA popstate so navigation is reflected in the canvas.
// Mines routes from four sources (in priority order):
// 1. window.__NEXT_DATA__ (Next.js Pages Router build manifest)
// 2. window.__next_router_basepath (App Router)
// 3. <a href> same-origin anchor links in the rendered DOM
// 4. Fiber nodeMap — Link/NavLink/NextLink component props
// Called 800 ms after READY and on every SPA popstate navigation.
function humanLabel(path) {
var seg = path.replace(/\/+$/, '').split('/').filter(function(s) { return s.length > 0; });
if (seg.length === 0) return 'Home';
@@ -494,24 +497,58 @@ export function buildProxyFiberHookScript(): string {
var routes = [];
function addRoute(path, hint) {
if (!path || seen[path]) return;
if (path.charAt(0) === '#') return;
seen[path] = true;
var label = (hint && typeof hint === 'string' && hint.trim().slice(0, 50)) || humanLabel(path);
routes.push({ path: path, label: label });
if (!path || typeof path !== 'string') return;
// Skip hash fragments, external links that slipped through, and dynamic segments
if (path.charAt(0) !== '/') return;
// Normalise: strip trailing slash except for root
var norm = path.length > 1 ? path.replace(/\\/+$/, '') : '/';
if (seen[norm]) return;
seen[norm] = true;
var label = (hint && typeof hint === 'string' && hint.trim().slice(0, 50)) || humanLabel(norm);
routes.push({ path: norm, label: label });
}
// ① Current page is always included
addRoute(window.location.pathname, document.title || undefined);
// ② Next.js Pages Router — __NEXT_DATA__ contains the full page list in build id manifest
try {
var nextData = window.__NEXT_DATA__;
if (nextData && nextData.buildId) {
// Fetch the pages manifest — available at /_next/static/{buildId}/_buildManifest.js
var manifestUrl = '/_next/static/' + nextData.buildId + '/_buildManifest.js';
var xhr = new XMLHttpRequest();
xhr.open('GET', manifestUrl, false); // sync — runs at init time before user interaction
xhr.send();
if (xhr.status === 200) {
// Manifest exposes self.__BUILD_MANIFEST = { sortedPages: [...] }
var match = xhr.responseText.match(/sortedPages\\s*:\\s*(\\[[^\\]]+\\])/);
if (match) {
try {
var pages = JSON.parse(match[1]);
pages.forEach(function(p) {
// Skip catch-all and dynamic segments for now; static routes only
if (p.indexOf('[') === -1) addRoute(p);
});
} catch (e) { /* parse failed */ }
}
}
}
} catch (e) { /* Next.js not present or manifest unavailable */ }
// ③ <a href> same-origin links from the rendered DOM
var anchors = document.querySelectorAll('a[href]');
for (var i = 0; i < anchors.length; i++) {
try {
var url = new URL(anchors[i].href, window.location.href);
if (url.origin !== window.location.origin) continue;
addRoute(url.pathname, anchors[i].textContent || undefined);
// Skip hash-only links
if (!url.pathname || url.hash && !url.pathname) continue;
addRoute(url.pathname, (anchors[i].textContent || '').trim() || undefined);
} catch (e) { /* skip malformed hrefs */ }
}
// ④ Fiber nodeMap — Link/NavLink props carry href/to
Object.keys(nodeMap).forEach(function(nid) {
var entry = nodeMap[nid];
var fiber = entry && entry.fiber;
@@ -519,7 +556,11 @@ export function buildProxyFiberHookScript(): string {
if (name && /^(Link|NavLink|NextLink|RouterLink|a)$/.test(name)) {
var props = fiber.memoizedProps;
var href = props && (props.href || props.to);
if (typeof href === 'string' && href.charAt(0) === '/') addRoute(href);
if (typeof href === 'string') addRoute(href);
// Next.js <Link href={{ pathname }}>
if (href && typeof href === 'object' && typeof href.pathname === 'string') {
addRoute(href.pathname);
}
}
});
@@ -529,6 +570,7 @@ export function buildProxyFiberHookScript(): string {
// ── Ready signal ──────────────────────────────────────────────────────────
post({ type: 'READY' });
setTimeout(discoverRoutes, 800);
// Re-discover on SPA navigation (Next.js App Router fires popstate on push)
window.addEventListener('popstate', function() { setTimeout(discoverRoutes, 100); });
})();`;
}
+4
View File
@@ -16,7 +16,11 @@ export type {
RendererEnvelope,
} from './protocol.js';
// React fiber hook — hooks into __REACT_DEVTOOLS_GLOBAL_HOOK__ to intercept
// React commits. Works only in React apps loaded in an Originmain iframe.
export { buildFiberHookScript, buildProxyFiberHookScript } from './fiber-hook.js';
// Universal DOM inspector — fallback for static HTML pages (React not required)
export { buildDomInspectorScript } from './dom-inspector.js';
export {
createRendererHostConfig,
+1 -1
View File
@@ -48,7 +48,7 @@ export interface HostEnvelope {
export type RendererMessage =
| { type: 'READY' }
| { type: 'FIBER_TREE_UPDATE'; root: FiberNode }
| { type: 'COMPONENT_SELECTED'; nodeId: string; rect: DOMRectLike }
| { type: 'COMPONENT_SELECTED'; nodeId: string; nodeName?: string; rect: DOMRectLike }
| { type: 'COMPONENT_DESELECTED' }
| { type: 'ERROR'; message: string }
/** Response to REQUEST_ELEMENT_STYLES — computed CSS properties for the node. */