QuantAscent — Account Profile Embed Spec (Webflow)¶
Audience: the web developer building quantascent.io in Webflow.
Companion to: docs/webflow_billing_integration.md (same account page).
Last updated: 2026-07-26
Why this exists¶
Today the desktop app's Manage Account button sends users to
accounts.quantascent.io/user — Clerk's hosted Account Portal. It looks
nothing like QuantAscent: Clerk's default purple accent, a stock page shell
with a large empty area below the card, and an orphaned logo thumbnail in the
corner.
Per Clerk's docs, Account Portal pages "cannot be customized beyond the
options provided in the Clerk Dashboard" — and the Dashboard exposes only
avatars, email/SMS templates, and paths. There is no color or font setting for
the portal. The purple is colorPrimary, reachable only through the
appearance prop, which applies solely to components we mount ourselves.
So: we mount the profile component on our account page instead. Same widget, our colors, our layout, no separate page to host.
What you're adding¶
One Embed element on the existing account page — the same page that already carries the status badge and the Start-free-trial / Manage-subscription buttons. Clerk is already loaded there for those buttons, so there is no new dependency.
[account page]
├─ status badge ← already built (GET /api/me)
├─ Start trial / Manage ← already built (billing spec)
└─ profile panel ← NEW: Clerk.mountUserProfile()
Nothing server-side changes. No new route, no new domain, no CORS edit.
Step 1 — the theme object¶
Paste this once, before the mount call. Values are lifted straight from the landing page's CSS custom properties so the widget matches the site exactly.
// QuantAscent Clerk theme — mirrors vps/landing/index.html :root tokens.
var QA_CLERK_APPEARANCE = {
variables: {
colorPrimary: '#00d4aa', // --qa-accent (replaces Clerk purple)
colorPrimaryForeground: '#0a0e14', // text ON the teal
colorBackground: '#161b22', // --qa-bg-2, the card
colorForeground: '#e6edf3', // --qa-fg
colorMutedForeground: '#8b949e', // --qa-fg-muted
colorMuted: '#1c2128', // --qa-bg-3
colorInput: '#0d1117', // --qa-bg-1
colorInputForeground: '#e6edf3',
colorBorder: '#30363d', // --qa-border
colorRing: 'rgba(0, 212, 170, 0.35)', // --qa-accent-border
colorShadow: 'rgba(0, 0, 0, 0.40)',
colorModalBackdrop: 'rgba(10, 14, 20, 0.80)',
colorNeutral: '#c9d1d9',
colorDanger: '#f85149',
colorSuccess: '#00d4aa',
colorWarning: '#d29922',
colorShimmer: '#21262d', // --qa-border-soft
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
borderRadius: '8px',
},
elements: {
// Our page section already draws a border; flatten Clerk's so they
// don't double up.
card: { boxShadow: 'none', border: 'none', backgroundColor: '#161b22' },
navbar: { backgroundColor: '#0d1117', borderColor: '#30363d' },
formButtonPrimary: {
backgroundColor: '#00d4aa',
color: '#0a0e14',
'&:hover': { backgroundColor: '#14e1b8' }, // --qa-accent-bright
},
formFieldInput: {
backgroundColor: '#0d1117',
borderColor: '#30363d',
color: '#e6edf3',
},
},
};
⚠️ Check your clerk-js version before pasting. Clerk renamed these variables in Core 3. The names above are the Core 3 / current set. If the site loads
@clerk/clerk-js@5(Core 2 — which is what our/downloadpage pins), use the older aliases instead:colorForeground→colorText,colorMutedForeground→colorTextSecondary,colorInput→colorInputBackground,colorInputForeground→colorInputText,colorPrimaryForeground→colorTextOnPrimaryBackground. Unknown keys are ignored silently, so a version mismatch shows up as "some colors didn't take" rather than an error. Confirm which version the site loads and tell us — we'd rather both surfaces run the same one.
Step 2 — the mount¶
<div id="qa-profile"></div>
<script>
// Wait for Clerk (already loaded on this page for the billing buttons).
function mountProfile() {
if (!window.Clerk || !window.Clerk.loaded) return;
if (!window.Clerk.user) return; // signed out — show sign-in instead
window.Clerk.mountUserProfile(
document.getElementById('qa-profile'),
{ appearance: QA_CLERK_APPEARANCE }
);
}
window.Clerk?.addListener(mountProfile);
mountProfile();
</script>
Guard against double-mounting if you already have a Clerk listener on the page —
Clerk fires it on every session change, and mounting twice stacks two widgets.
A module-scope var profileMounted = false; flag is enough.
Step 3 — container styling¶
The widget sizes to its container. Give it the page's normal content width rather than letting it float:
#qa-profile {
width: 100%;
max-width: 880px;
margin: 0 auto;
}
#qa-profile > * { margin-left: auto; margin-right: auto; }
This is what kills the dead space on the hosted portal — there, the page shell is Clerk's and fixed; here it's ours.
What this does and doesn't cover¶
| Surface | After this change |
|---|---|
| Profile, email addresses, connected accounts, password, 2FA | ✅ On our account page, our colors |
| Sign-in / sign-up / password reset | ❌ Still the hosted portal (light-themed, Clerk purple). Separate decision — see below. |
| "Secured by Clerk" footer | ❌ Stays. Removing it is a paid-plan feature; we're on Hobby. |
| Verification / invite emails | ❌ Dashboard → Customization → Emails, separate template work. |
Open item for QuantAscent (not the web dev)¶
Once this ships, the desktop app's Manage Account ↗ button
(frontend/views/settings_view.py:2129) should point at the account page
instead of accounts.quantascent.io/user. That's a one-string change, but it
rides the same unresolved apex-routing question already flagged in
plans/webflow_weekend_billing_session.md — shipped desktop builds hardcode
quantascent.io/desktop-signin, and /subscribe, /billing/done, /download
live on the VPS at the same apex Webflow is taking over. Decide the routing
approach for all of them together; don't special-case this button.