From 10993475d32cf97c31042fbbcafdc33b2f4fdd62 Mon Sep 17 00:00:00 2001 From: Sergii Demianchuk Date: Sat, 25 Jul 2026 23:07:35 -0400 Subject: [PATCH] feat(ai): floating-chevron hero + docs link on the account modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elevates the signed-in Manage-account popover from clean-but-plain to the landing page's signature moment. - Hero: the levelcode.ai "portal" mark — three colored chevrons rising over four soft circles that drift (portalDrift 7s, staggered -1.4s per layer), replacing the plain SD initials. Decorative + aria-hidden; honors prefers-reduced-motion. - The modal is now a proper premium dialog: full-bleed hero, 14px radius, soft shadow, entrance pop. - Documentation link -> https://levelcode.ai/ai/docs, in a tidy footer beside the Synced line. Routed through the SAME fixed allow-list as Terms/Privacy (target 'docs' -> /ai/docs); never a webview-supplied URL, so the open-redirect guard holds. Still model-agnostic; light/One Dark keep the brand palette; high-contrast still defers to editor tokens (the hero is decorative). extension.js: openLegal() gains the 'docs' target. Verified: both files parse; full gate 24 suites; structure present. Matches the approved mockup; a live in-editor render needs the VS Code host. Co-Authored-By: Claude Opus 4.8 --- extensions/levelcode-ai/extension.js | 6 +- extensions/levelcode-ai/media/chat.html | 76 ++++++++++++++++++------- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/extensions/levelcode-ai/extension.js b/extensions/levelcode-ai/extension.js index e6a67fc..834b106 100644 --- a/extensions/levelcode-ai/extension.js +++ b/extensions/levelcode-ai/extension.js @@ -1161,10 +1161,10 @@ async function openUpgrade() { await vscode.env.openExternal(vscode.Uri.parse(base + '/ai/pricing')); } -/** Open a LevelCode legal page (Terms / Privacy) from the sign-in modal. Targets are a fixed - * allow-list — never a URL from the webview — so this can't be turned into an open-redirect. */ +/** Open a fixed LevelCode site page (Terms / Privacy / Docs). Targets are a fixed allow-list — never a + * URL from the webview — so this can't be turned into an open-redirect. */ async function openLegal(target) { - const path = target === 'privacy' ? '/privacy' : target === 'terms' ? '/terms' : null; + const path = target === 'privacy' ? '/privacy' : target === 'terms' ? '/terms' : target === 'docs' ? '/ai/docs' : null; if (!path) { return; } await vscode.env.openExternal(vscode.Uri.parse('https://levelcode.ai' + path)); } diff --git a/extensions/levelcode-ai/media/chat.html b/extensions/levelcode-ai/media/chat.html index ed2f02d..98e93a8 100644 --- a/extensions/levelcode-ai/media/chat.html +++ b/extensions/levelcode-ai/media/chat.html @@ -849,9 +849,9 @@ --cc-line: #3e4451; --cc-accent: #7d6bff; --cc-mono: var(--vscode-editor-font-family, ui-monospace, "SF Mono", Menlo, Consolas, monospace); font-variant-numeric: tabular-nums; /* align digits across the card (email, any future counters) */ - max-width: 300px; padding: 24px 22px 20px; border-radius: 6px; + max-width: 304px; padding: 0; border-radius: 14px; overflow: hidden; background: var(--cc-surface); color: var(--cc-text); border: 1px solid var(--cc-line); - box-shadow: 0 1px 2px rgba(0,0,0,.05); + box-shadow: 0 18px 50px rgba(0,0,0,.34); animation: lcPop .4s cubic-bezier(.2,.9,.25,1.2) both; } body.vscode-light .acctcard.classic { --cc-bg: #ffffff; --cc-surface: #fafaf9; --cc-text: #333333; --cc-text2: #555555; --cc-text3: #777777; @@ -876,12 +876,18 @@ } .acctcard.classic .ovclose { color: var(--cc-text3); } .acctcard.classic .ovclose:hover { color: var(--cc-text); background: color-mix(in srgb, var(--cc-text3) 18%, transparent); } - .acctcard.classic .bigavatar { - width: 56px; height: 56px; border-radius: 50%; margin: 2px auto 14px; display: grid; place-items: center; - border: 1px solid color-mix(in srgb, var(--cc-accent) 42%, var(--cc-line)); - background: color-mix(in srgb, var(--cc-accent) 13%, transparent); color: var(--cc-accent); - font-weight: 600; font-size: 18px; letter-spacing: .02em; - } + /* Hero — the landing page's floating chevron over four drifting "portal" circles. Decorative (aria-hidden), + full-bleed at the top of the modal; the motion respects prefers-reduced-motion (guard below). */ + .acctcard.classic .hero { position: relative; height: 150px; display: grid; place-items: center; overflow: hidden; } + .acctcard.classic .hero .portalmark { width: 212px; height: auto; display: block; } + .acctcard.classic .portal-layer { transform-origin: 50% 60%; animation: portalDrift 7s ease-in-out infinite; } + .acctcard.classic .portal-layer:nth-child(2) { animation-delay: -1.4s; } + .acctcard.classic .portal-layer:nth-child(3) { animation-delay: -2.8s; } + .acctcard.classic .portal-layer:nth-child(4) { animation-delay: -4.2s; } + .acctcard.classic .portal-layer:nth-child(5) { animation-delay: -5.6s; } + @keyframes portalDrift { 0%, 100% { transform: translateY(0) scale(1); } 50% { transform: translateY(-6px) scale(1.02); } } + /* Everything under the hero carries the padding, since the hero is full-bleed. */ + .acctcard.classic .acctmain { padding: 2px 24px 22px; } .acctcard.classic h2 { margin: 0; font-size: 20px; font-weight: 700; letter-spacing: -.01em; color: var(--cc-text); } .acctcard.classic .em { margin: 5px 0 0; font-family: var(--cc-mono); font-size: 12.5px; color: var(--cc-text3); } .acctcard.classic .planpill { @@ -902,14 +908,27 @@ } .acctcard.classic .signout:hover { text-decoration: underline; } .acctcard.classic .signout:focus-visible { outline: 2px solid var(--cc-accent); outline-offset: 2px; } - .acctcard.classic .synced { + /* Footer: the Documentation link + the muted "Synced" line, under one divider. */ + .acctcard.classic .acctfoot { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--cc-line); - font-family: var(--cc-mono); font-size: 11.5px; color: var(--cc-text3); line-height: 1.5; + display: flex; flex-direction: column; align-items: center; gap: 9px; + } + .acctcard.classic .docs { + display: inline-flex; align-items: center; gap: 6px; padding: 1px 4px; border-radius: 4px; + color: var(--cc-accent); font-size: 13px; font-weight: 500; text-decoration: none; cursor: pointer; } + .acctcard.classic .docs:hover { text-decoration: underline; } + .acctcard.classic .docs:focus-visible { outline: 2px solid var(--cc-accent); outline-offset: 2px; } + .acctcard.classic .docs .darrow { font-size: 11px; opacity: .85; } + .acctcard.classic .synced { font-family: var(--cc-mono); font-size: 11px; color: var(--cc-text3); line-height: 1.5; } .acctcard.classic .note { margin-top: 12px; font-family: var(--cc-mono); font-size: 11.5px; color: var(--cc-text3); line-height: 1.55; text-align: left; } + @media (prefers-reduced-motion: reduce) { + .acctcard.classic { animation: none; } + .acctcard.classic .portal-layer { animation: none; } + } /* ── LevelCode sign-in modal — calm, soft, THEME-FOLLOWING (matches the Manage-account card) ──── Scoped to .acctcard.lc-modal. Uses the editor theme tokens (--vscode-*, --border, --muted, --field-bg) @@ -2740,6 +2759,20 @@ ''; + // The full COLORED "portal" mark from the levelcode.ai landing hero: three chevrons over four soft + // drifting circles (the .portal-layer drift lives in CSS). Decorative; aria-hidden. First circle takes + // --cc-accent (theme-aware); the rest are the fixed brand gradient. + const LC_PORTAL_SVG = + ''; const LC_GH_SVG = ''; const LC_GOOGLE_SVG = @@ -2782,16 +2815,19 @@ // Gateway needs no explainer (the "how the gateway works" blurb was removed). BYOK keeps a one-line // privacy reassurance, which is genuinely useful there. const note = gateway ? '' : '
Your keys never leave your machine — requests go direct to your provider.
'; + const docsLink = 'Documentation '; acctBody.innerHTML = - '
' + esc(initials(account.name || account.email)) + '
' - + '

' + esc(account.name || 'Signed in') + '

' - + emailLine - + planPill - + upgradeBtn - + '' - + '' - + '
Synced · settings · skills · keymaps
' - + note; + '
' + LC_PORTAL_SVG + '
' + + '
' + + '

' + esc(account.name || 'Signed in') + '

' + + emailLine + + planPill + + upgradeBtn + + '' + + '' + + '
' + docsLink + '
Synced · settings · skills · keymaps
' + + note + + '
'; } else { acctCard.classList.remove('classic'); acctCard.classList.add('lc-modal'); // signed-out → the calm LevelCode sign-in modal @@ -2866,7 +2902,7 @@ else if (act === 'manage') { vscode.postMessage({ type: 'accountManage' }); } else if (act === 'github' || act === 'google') { vscode.postMessage({ type: 'accountSignIn', provider: act }); } else if (act === 'create') { vscode.postMessage({ type: 'accountSignIn', create: true }); } - else if (act === 'terms' || act === 'privacy') { vscode.postMessage({ type: 'openExternal', target: act }); } + else if (act === 'terms' || act === 'privacy' || act === 'docs') { vscode.postMessage({ type: 'openExternal', target: act }); } else { vscode.postMessage({ type: 'accountSignIn' }); } } acctBody.addEventListener('click', (e) => {