Skip to content

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 /download page pins), use the older aliases instead: colorForegroundcolorText, colorMutedForegroundcolorTextSecondary, colorInputcolorInputBackground, colorInputForegroundcolorInputText, colorPrimaryForegroundcolorTextOnPrimaryBackground. 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.