diff --git a/frontend.log b/frontend.log new file mode 100644 index 0000000..f755a31 --- /dev/null +++ b/frontend.log @@ -0,0 +1,21 @@ + +> dev +> vinxi dev + +vinxi v0.5.11 +vinxi found vinxi app config in vite.config.ts +vinxi starting dev server +[get-port] Unable to find an available port (tried 3000 on host "localhost"). Using alternative port 3001. + + ➜ Local: http://localhost:3001/ + ➜ Network: use --host to expose + +2:43:36 AM [vite] (client) page reload live-demo.cjs +2:47:39 AM [vite] (ssr) hmr update /src/app.css +2:47:39 AM [vite] (client) hmr update /src/app.css +2:47:39 AM [vite] (ssr) hmr update /src/app.css +2:47:39 AM [vite] (client) hmr update /src/app.css +2:48:23 AM [vite] (ssr) hmr update /src/app.css +2:48:23 AM [vite] (client) hmr update /src/app.css +2:48:38 AM [vite] (ssr) hmr update /src/app.css +2:48:38 AM [vite] (client) hmr update /src/app.css diff --git a/live-demo-manual.cjs b/live-demo-manual.cjs new file mode 100644 index 0000000..a7ffb68 --- /dev/null +++ b/live-demo-manual.cjs @@ -0,0 +1,176 @@ +const { chromium } = require("playwright"); +const { randomUUID } = require("crypto"); + +(async () => { + console.log("🎭 Starting Live UI Testing Demo\n"); + + const testEmail = `testcompany${randomUUID().slice(0, 8)}@test.com`; + const testPassword = "TestPass123!"; + const testCompanyName = `Test Company ${randomUUID().slice(0, 6)}`; + const firstName = "John"; + const lastName = "Doe"; + + console.log("πŸ“§ Test Email:", testEmail); + console.log("πŸ‘€ Name:", firstName, lastName); + console.log("🏒 Company Name:", testCompanyName); + console.log("\nπŸš€ Launching Chromium browser...\n"); + + const browser = await chromium.launch({ + headless: false, + slowMo: 600, + args: ["--window-size=1400,900"], + }); + + const context = await browser.newContext({ + viewport: { width: 1400, height: 900 }, + }); + + const page = await context.newPage(); + + try { + // Step 1: Navigate to signup + console.log("πŸ“ Step 1: Opening signup page..."); + await page.goto("http://localhost:3001/signup?intent=company"); + await page.waitForTimeout(3000); + + // Step 2: Fill ALL required fields using correct selectors + console.log("✍️ Step 2: Filling ALL required fields...\n"); + + // First Name (id="first-name") + console.log(" β†’ First Name..."); + await page.fill("#first-name", firstName); + await page.waitForTimeout(300); + + // Last Name (id="last-name") + console.log(" β†’ Last Name..."); + await page.fill("#last-name", lastName); + await page.waitForTimeout(300); + + // Email (id="email") + console.log(" β†’ Email..."); + await page.fill("#email", testEmail); + await page.waitForTimeout(300); + + // Password (id="password") + console.log(" β†’ Password..."); + await page.fill("#password", testPassword); + await page.waitForTimeout(300); + + // Confirm Password (id="confirm-password") + console.log(" β†’ Confirm Password..."); + await page.fill("#confirm-password", testPassword); + await page.waitForTimeout(300); + + // CAPTCHA (id="captcha") - Need to read from canvas + console.log(" β†’ CAPTCHA..."); + // For demo purposes, we'll need to manually handle this or skip + // The captcha is drawn on a canvas, we can't easily read it + // Let's fill a placeholder and see what happens + await page.fill("#captcha", "TEST12"); + console.log(" ⚠️ Note: CAPTCHA filled with placeholder"); + await page.waitForTimeout(300); + + // Terms Checkbox + console.log(" β†’ Terms & Conditions..."); + await page.check(".auth-checkbox"); + console.log(" βœ… Terms checked"); + await page.waitForTimeout(300); + + console.log("\nβœ… All fields filled!"); + await page.waitForTimeout(1500); + + // Take screenshot before submit + await page.screenshot({ path: "./test-results/02-signup-filled.png", fullPage: true }); + + // Step 3: Check if button is enabled + console.log("\nπŸ“€ Step 3: Checking Sign Up button..."); + const submitBtn = await page.locator(".auth-submit-btn"); + const isDisabled = await submitBtn.isDisabled(); + + if (isDisabled) { + console.log("⚠️ Button is still disabled - CAPTCHA may be invalid"); + console.log(" πŸ“ In real usage, user would need to:"); + console.log(" 1. Read the CAPTCHA from the canvas image"); + console.log(" 2. Enter the correct CAPTCHA code"); + console.log(" 3. Then click Sign Up"); + + // For demo, let's try refreshing the captcha and entering a new one + console.log(" πŸ”„ Refreshing CAPTCHA..."); + await page.click(".auth-captcha-refresh"); + await page.waitForTimeout(1000); + + // Note: We still can't read the canvas captcha programmatically + // This is where manual intervention is needed in real testing + } + + console.log("\n⏸️ PAUSED: Manual CAPTCHA entry required"); + console.log(" Please manually:"); + console.log(" 1. Look at the CAPTCHA image in the browser"); + console.log(" 2. Clear the CAPTCHA field"); + console.log(" 3. Enter the correct CAPTCHA code"); + console.log(" 4. Click the Sign Up button"); + console.log(" 5. Complete the email verification"); + console.log("\n Browser will stay open for 60 seconds..."); + console.log(" (Take your time to complete the flow manually)"); + + // Keep browser open for manual interaction + await new Promise((resolve) => setTimeout(resolve, 60000)); + + // After manual completion, check current URL + const currentUrl = page.url(); + console.log("\nπŸ“ Current URL after manual flow:", currentUrl); + + // If we reached the dashboard or verification page + if (currentUrl.includes("dashboard") || currentUrl.includes("verify")) { + console.log("βœ… Signup flow completed!"); + await page.screenshot({ path: "./test-results/03-after-signup.png", fullPage: true }); + + // Now check admin panel + console.log("\nπŸ” Opening Admin Panel to check verification..."); + const adminPage = await context.newPage(); + await adminPage.goto("http://localhost:3000/login"); + await adminPage.waitForTimeout(2000); + + // Admin login + await adminPage.fill('input[type="email"]', "admin@nxtgauge.com"); + await adminPage.fill('input[type="password"]', "admin123"); + await adminPage.click('button[type="submit"]'); + await adminPage.waitForTimeout(3000); + + // Go to verification + await adminPage.goto("http://localhost:3000/admin/verification"); + await adminPage.waitForTimeout(3000); + + // Search for company + const searchInput = await adminPage.locator('input[type="search"]').first(); + if (searchInput) { + await searchInput.fill(testEmail); + await adminPage.waitForTimeout(2000); + } + + await adminPage.screenshot({ + path: "./test-results/04-admin-verification.png", + fullPage: true, + }); + console.log("πŸ“Έ Admin screenshot saved"); + + const companyFound = await adminPage + .locator(`text=${testEmail}`) + .isVisible() + .catch(() => false); + if (companyFound) { + console.log("βœ… SUCCESS! Company found in verification queue!"); + } else { + console.log("⚠️ Company not found - may still be processing"); + } + } + } catch (error) { + console.error("\n❌ Error:", error.message); + await page.screenshot({ path: "./test-results/error.png", fullPage: true }); + } finally { + await context.close(); + await browser.close(); + console.log("\nβœ… Test completed"); + console.log("πŸ“Έ Screenshots in ./test-results/"); + } +})(); diff --git a/live-demo.cjs b/live-demo.cjs new file mode 100644 index 0000000..95d8a5b --- /dev/null +++ b/live-demo.cjs @@ -0,0 +1,357 @@ +const { chromium } = require("playwright"); +const { randomUUID } = require("crypto"); + +(async () => { + console.log("🎭 Starting Live UI Testing Demo\n"); + + const testEmail = `testcompany${randomUUID().slice(0, 8)}@test.com`; + const testPassword = "TestPassword123!"; + const testCompanyName = `Test Company ${randomUUID().slice(0, 6)}`; + const firstName = "John"; + const lastName = "Doe"; + + console.log("πŸ“§ Test Email:", testEmail); + console.log("πŸ‘€ Name:", firstName, lastName); + console.log("🏒 Company Name:", testCompanyName); + console.log("\nπŸš€ Launching Chromium browser with visible window...\n"); + + const browser = await chromium.launch({ + headless: false, + slowMo: 800, + args: ["--window-size=1400,900"], + }); + + const context = await browser.newContext({ + viewport: { width: 1400, height: 900 }, + }); + + const page = await context.newPage(); + + try { + // Step 1: Navigate to signup + console.log("πŸ“ Step 1: Opening signup page..."); + await page.goto("http://localhost:3001/signup?intent=company"); + await page.waitForTimeout(3000); + + // Step 2: Fill ALL required fields + console.log("✍️ Step 2: Filling ALL required fields...\n"); + + // First Name + console.log(" β†’ First Name..."); + const firstNameInput = await page + .locator('input[name="firstName"], input[placeholder*="First"]') + .first(); + await firstNameInput.fill(firstName); + await page.waitForTimeout(500); + + // Last Name + console.log(" β†’ Last Name..."); + const lastNameInput = await page + .locator('input[name="lastName"], input[placeholder*="Last"]') + .first(); + await lastNameInput.fill(lastName); + await page.waitForTimeout(500); + + // Email + console.log(" β†’ Email..."); + const emailInput = await page + .locator('input[type="email"], input[name="email"]') + .first(); + await emailInput.fill(testEmail); + await page.waitForTimeout(500); + + // Password + console.log(" β†’ Password..."); + const passwordInput = await page.locator('input[name="password"]').first(); + await passwordInput.fill(testPassword); + await page.waitForTimeout(500); + + // Confirm Password + console.log(" β†’ Confirm Password..."); + const confirmInput = await page + .locator('input[name="confirmPassword"], input[name="confirm"]') + .first(); + await confirmInput.fill(testPassword); + await page.waitForTimeout(500); + + // CAPTCHA - Read the code from the canvas/image + console.log(" β†’ Reading CAPTCHA code..."); + const captchaText = await page + .locator(".captcha-code, [data-captcha], .captcha-display") + .textContent() + .catch(() => null); + if (captchaText) { + console.log(" β†’ CAPTCHA found:", captchaText.trim()); + const captchaInput = await page + .locator('input[name="captcha"], input[placeholder*="CAPTCHA"]') + .first(); + await captchaInput.fill(captchaText.trim()); + } else { + console.log(" β†’ CAPTCHA not readable, will try to find input..."); + // Try to get from a data attribute or regenerate + const captchaInput = await page.locator('input[name="captcha"]').first(); + if (captchaInput) { + // Look for any visible captcha text + const captchaCode = await page.evaluate(() => { + const el = document.querySelector( + ".captcha-code, .captcha-text, [data-captcha]", + ); + return el ? el.textContent || el.getAttribute("data-captcha") : null; + }); + if (captchaCode) { + await captchaInput.fill(captchaCode.trim()); + console.log(" β†’ CAPTCHA filled:", captchaCode.trim()); + } + } + } + await page.waitForTimeout(500); + + // Terms Checkbox + console.log(" β†’ Checking Terms & Conditions..."); + const termsCheckbox = await page + .locator( + 'input[type="checkbox"][name*="terms"], input[type="checkbox"][name*="agree"]', + ) + .first(); + if (termsCheckbox) { + await termsCheckbox.check(); + console.log(" βœ… Terms checkbox checked"); + } + await page.waitForTimeout(500); + + console.log("\nβœ… All fields filled!"); + await page.waitForTimeout(2000); + + // Step 3: Submit signup + console.log("\nπŸ“€ Step 3: Clicking Sign Up button..."); + const submitBtn = await page + .locator( + 'button[type="submit"]:has-text("Sign"), button:has-text("Create"), button:has-text("Register")', + ) + .first(); + + // Check if button is enabled + const isDisabled = await submitBtn.isDisabled().catch(() => false); + if (isDisabled) { + console.log("⚠️ Button is disabled - checking what field is missing..."); + // Take screenshot to debug + await page.screenshot({ + path: "./test-results/signup-debug.png", + fullPage: true, + }); + } else { + await submitBtn.click(); + console.log("βœ… Sign Up button clicked"); + } + await page.waitForTimeout(5000); + + // Step 4: Check current page + const currentUrl = page.url(); + console.log("\nπŸ“ Current URL:", currentUrl); + + if (currentUrl.includes("verify") || currentUrl.includes("otp")) { + console.log("πŸ“§ Step 4: OTP verification page detected"); + console.log(" β†’ Entering OTP..."); + + // Fill OTP (6 digits) + const otpDigits = ["1", "2", "3", "4", "5", "6"]; + for (let i = 0; i < 6; i++) { + const otpInput = await page + .locator(`input[name="otp${i}"], input[aria-label*="digit ${i + 1}"]`) + .first(); + if (otpInput) { + await otpInput.fill(otpDigits[i]); + await page.waitForTimeout(200); + } + } + + // Submit OTP + const verifyBtn = await page + .locator('button:has-text("Verify"), button[type="submit"]') + .first(); + await verifyBtn.click(); + console.log("βœ… OTP submitted"); + await page.waitForTimeout(4000); + } + + // Step 5: Select Company role if on role selection page + console.log("\n🎯 Step 5: Checking for role selection..."); + const roleOption = await page.locator("text=Company").first(); + if (await roleOption.isVisible().catch(() => false)) { + await roleOption.click(); + console.log("βœ… Company role selected"); + await page.waitForTimeout(3000); + } + + // Step 6: Fill Company Profile + console.log("\nπŸ“ Step 6: Filling company profile..."); + + // Company name + const nameInputs = await page + .locator( + 'input[name*="company"], input[name*="name"], input[placeholder*="Company"]', + ) + .all(); + for (const input of nameInputs.slice(0, 3)) { + try { + const placeholder = await input.getAttribute("placeholder"); + const name = await input.getAttribute("name"); + + if ( + placeholder?.toLowerCase().includes("company") || + name?.toLowerCase().includes("company") + ) { + await input.fill(testCompanyName); + console.log(" βœ… Company name:", testCompanyName); + break; + } + } catch (e) {} + } + await page.waitForTimeout(1000); + + // Fill other profile fields + const inputs = await page.locator("input, textarea, select").all(); + for (const input of inputs) { + try { + const type = await input.getAttribute("type"); + const name = await input.getAttribute("name"); + const tagName = await input.evaluate((el) => el.tagName.toLowerCase()); + + if (tagName === "select") { + // Select dropdown + const options = await input.locator("option").all(); + if (options.length > 1) { + await input.selectOption({ index: 1 }); + } + } else if (type === "email" || name?.includes("email")) { + await input.fill(testEmail); + } else if (name?.includes("phone") || name?.includes("contact")) { + await input.fill("+91 9876543210"); + } else if (name?.includes("city")) { + await input.fill("Bangalore"); + } else if (name?.includes("state")) { + await input.fill("Karnataka"); + } else if (name?.includes("country")) { + await input.fill("India"); + } else if (name?.includes("address")) { + await input.fill("123 Tech Park, Bangalore"); + } else if (name?.includes("website")) { + await input.fill("https://testcompany.com"); + } else if (name?.includes("industry")) { + await input.fill("Technology"); + } else if (name?.includes("description") || name?.includes("about")) { + await input.fill( + "We are a technology company looking for talented professionals to join our team. We offer competitive salaries and a great work environment.", + ); + } else if (type === "text" && !name?.includes("name")) { + // Fill any empty text fields + const value = await input.inputValue(); + if (!value) { + await input.fill("Test Data"); + } + } + } catch (e) {} + } + console.log(" βœ… Profile fields filled"); + await page.waitForTimeout(2000); + + // Step 7: Submit for verification + console.log("\nπŸš€ Step 7: Submitting profile for verification..."); + const profileSubmitBtn = await page + .locator( + 'button[type="submit"], button:has-text("Submit"), button:has-text("Save"), button:has-text("Continue")', + ) + .first(); + await profileSubmitBtn.click(); + console.log("βœ… Profile submitted"); + await page.waitForTimeout(5000); + + // Take screenshot + await page.screenshot({ + path: "./test-results/after-profile-submit.png", + fullPage: true, + }); + console.log("πŸ“Έ Screenshot saved: after-profile-submit.png"); + + // Step 8: Open Admin Panel in new tab + console.log("\nπŸ” Step 8: Opening admin panel..."); + const adminPage = await context.newPage(); + await adminPage.goto("http://localhost:3000/login"); + await adminPage.waitForTimeout(3000); + + // Admin login + console.log("πŸ”‘ Logging into admin panel..."); + const adminEmailInput = await adminPage + .locator('input[type="email"], input[name="email"]') + .first(); + await adminEmailInput.fill("admin@nxtgauge.com"); + await adminPage.waitForTimeout(500); + + const adminPasswordInput = await adminPage + .locator('input[type="password"]') + .first(); + await adminPasswordInput.fill("admin123"); + await adminPage.waitForTimeout(500); + + const adminLoginBtn = await adminPage + .locator('button[type="submit"]') + .first(); + await adminLoginBtn.click(); + console.log("βœ… Admin login submitted"); + await adminPage.waitForTimeout(4000); + + // Step 9: Navigate to Verifications + console.log("\nπŸ” Step 9: Navigating to Verification Management..."); + await adminPage.goto("http://localhost:3000/admin/verification"); + await adminPage.waitForTimeout(4000); + + // Step 10: Search for the company + console.log("\nπŸ”Ž Step 10: Searching for submitted company..."); + const searchInput = await adminPage + .locator( + 'input[type="search"], input[name="search"], input[placeholder*="search"]', + ) + .first(); + if (searchInput) { + await searchInput.fill(testCompanyName); + console.log(` βœ… Searching for: ${testCompanyName}`); + await adminPage.waitForTimeout(3000); + } + + // Take final screenshot + await adminPage.screenshot({ + path: "./test-results/admin-verification.png", + fullPage: true, + }); + console.log("πŸ“Έ Screenshot saved: admin-verification.png"); + + // Check if company is found + const companyFound = await adminPage + .locator(`text=${testCompanyName}`) + .isVisible() + .catch(() => false); + if (companyFound) { + console.log( + "\nβœ… SUCCESS! Company verification request found in admin panel!", + ); + console.log(` Company: ${testCompanyName}`); + console.log(` Email: ${testEmail}`); + } else { + console.log("\n⚠️ Company not immediately visible. Check:"); + console.log(" - Filters may need adjustment"); + console.log(" - Verification may still be processing"); + console.log(" - Try searching by email instead"); + } + + console.log("\n⏳ Keeping browser open for 20 seconds..."); + console.log(" Screenshots saved in ./test-results/"); + await new Promise((resolve) => setTimeout(resolve, 20000)); + } catch (error) { + console.error("\n❌ Error:", error.message); + await page.screenshot({ path: "./test-results/error.png", fullPage: true }); + } finally { + await context.close(); + await browser.close(); + console.log("\nβœ… Test completed"); + } +})(); diff --git a/src/app.css b/src/app.css index ed752f5..11c93c6 100644 --- a/src/app.css +++ b/src/app.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Exo+2:wght@400;500;600;700;800&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Exo+2:wght@400;500;600;700;800&display=swap"); @import "tailwindcss"; :root { @@ -22,10 +22,12 @@ select { body { margin: 0; - font-family: 'Exo 2', sans-serif; + font-family: "Exo 2", sans-serif; color: var(--ink); scrollbar-gutter: stable; background: #07051a; + overflow-y: auto; + overflow-x: hidden; } .container { @@ -748,7 +750,10 @@ body { border-bottom: 1px solid rgba(16, 11, 47, 0.1); background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(24px); - transition: box-shadow 300ms ease, border-color 300ms ease, background-color 300ms ease; + transition: + box-shadow 300ms ease, + border-color 300ms ease, + background-color 300ms ease; } .public-header-scrolled { @@ -786,7 +791,7 @@ body { } .public-header .nav-links a::after { - content: ''; + content: ""; position: absolute; left: 0; bottom: -4px; @@ -807,12 +812,12 @@ body { } .public-header .nav-links a.active, -.public-header .nav-links a[aria-current='page'] { +.public-header .nav-links a[aria-current="page"] { color: #fd6116; } .public-header .nav-links a.active::after, -.public-header .nav-links a[aria-current='page']::after { +.public-header .nav-links a[aria-current="page"]::after { transform: scaleX(0); } @@ -827,12 +832,18 @@ body { text-decoration: none; font-size: 14px; font-weight: 600; - transition: transform 180ms ease, box-shadow 220ms ease, border-color 220ms ease, background-color 220ms ease; + transition: + transform 180ms ease, + box-shadow 220ms ease, + border-color 220ms ease, + background-color 220ms ease; } .nav-auth-btn:hover { transform: translateY(-1px); - box-shadow: 0 0 0 3px rgba(253, 98, 22, 0.13), 0 12px 22px -14px rgba(2, 6, 23, 0.55); + box-shadow: + 0 0 0 3px rgba(253, 98, 22, 0.13), + 0 12px 22px -14px rgba(2, 6, 23, 0.55); } .nav-auth-secondary { @@ -849,7 +860,9 @@ body { border: 1px solid rgba(253, 98, 22, 0.72); background: #fd6116; color: #fff; - box-shadow: 0 0 0 1px rgba(253, 98, 22, 0.4) inset, 0 12px 24px -16px rgba(253, 98, 22, 0.9); + box-shadow: + 0 0 0 1px rgba(253, 98, 22, 0.4) inset, + 0 12px 24px -16px rgba(253, 98, 22, 0.9); } .nav-auth-primary:hover { @@ -961,7 +974,7 @@ body { } .scene-light::before { - content: ''; + content: ""; position: absolute; inset: 8px 0; background: @@ -1104,7 +1117,11 @@ body { font-size: 14px; font-weight: 600; cursor: pointer; - transition: border-color 180ms ease, background-color 180ms ease, color 180ms ease, box-shadow 220ms ease; + transition: + border-color 180ms ease, + background-color 180ms ease, + color 180ms ease, + box-shadow 220ms ease; } .chip-btn:hover { @@ -1132,11 +1149,14 @@ body { border: 1px solid rgba(16, 11, 47, 0.12); background: rgba(255, 255, 255, 0.92); backdrop-filter: blur(14px); - transition: transform 240ms ease, border-color 240ms ease, box-shadow 240ms ease; + transition: + transform 240ms ease, + border-color 240ms ease, + box-shadow 240ms ease; } .path-card::before { - content: ''; + content: ""; position: absolute; inset: -30%; background: radial-gradient(circle at 50% 50%, rgba(253, 98, 22, 0.18), transparent 60%); @@ -1254,7 +1274,9 @@ body { font-weight: 600; color: #100b2f; text-decoration: none; - transition: border-color 180ms ease, color 180ms ease; + transition: + border-color 180ms ease, + color 180ms ease; } .path-secondary-btn:hover { @@ -1287,7 +1309,10 @@ body { background: rgba(255, 255, 255, 0.92); padding: 14px; box-shadow: 0 12px 28px -26px rgba(253, 98, 22, 0.9); - transition: transform 200ms ease, box-shadow 200ms ease, border-color 200ms ease; + transition: + transform 200ms ease, + box-shadow 200ms ease, + border-color 200ms ease; } .role-benefit-card:hover { @@ -1639,7 +1664,10 @@ body { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(8px); overflow: hidden; - transition: border-color 220ms ease, background-color 220ms ease, box-shadow 220ms ease; + transition: + border-color 220ms ease, + background-color 220ms ease, + box-shadow 220ms ease; } .faq-item.open { @@ -1683,7 +1711,9 @@ body { line-height: 1; color: rgba(255, 255, 255, 0.74); transform: rotate(0deg); - transition: transform 220ms ease, color 220ms ease; + transition: + transform 220ms ease, + color 220ms ease; } .faq-q-icon.open { @@ -1766,7 +1796,10 @@ body { gap: 10px; align-items: start; box-shadow: 0 12px 28px -24px rgba(253, 98, 22, 0.55); - transition: transform 220ms ease, box-shadow 220ms ease, border-color 220ms ease; + transition: + transform 220ms ease, + box-shadow 220ms ease, + border-color 220ms ease; } .pro-detail-trust-card .role-card-icon { @@ -2124,7 +2157,7 @@ body { .lp-main { position: relative; min-height: 100vh; - overflow-x: clip; + overflow-x: hidden; isolation: isolate; background: #07051a; } @@ -2165,7 +2198,12 @@ body { .lp-ribbon { position: absolute; inset: -18% -6%; - background: linear-gradient(120deg, transparent 35%, rgba(253, 98, 22, 0.18) 55%, transparent 74%); + background: linear-gradient( + 120deg, + transparent 35%, + rgba(253, 98, 22, 0.18) 55%, + transparent 74% + ); filter: blur(14px); opacity: 0.54; will-change: transform; @@ -2176,7 +2214,11 @@ body { position: absolute; inset: 0; opacity: 0.05; - background-image: radial-gradient(circle at 1px 1px, rgba(255, 255, 255, 0.38) 1px, transparent 0); + background-image: radial-gradient( + circle at 1px 1px, + rgba(255, 255, 255, 0.38) 1px, + transparent 0 + ); background-size: 4px 4px; mix-blend-mode: soft-light; } @@ -2405,22 +2447,33 @@ body { } .role-typical-shell::before { - content: ''; + content: ""; position: absolute; inset: 0; - background: linear-gradient(120deg, rgba(255, 255, 255, 0.08), transparent 36%, rgba(255, 255, 255, 0.05) 64%, transparent); + background: linear-gradient( + 120deg, + rgba(255, 255, 255, 0.08), + transparent 36%, + rgba(255, 255, 255, 0.05) 64%, + transparent + ); pointer-events: none; } .role-typical-shell::after { - content: ''; + content: ""; position: absolute; width: 54%; height: 64%; right: -6%; top: -14%; border-radius: 999px; - background: radial-gradient(circle at center, rgba(253, 98, 22, 0.32), rgba(253, 98, 22, 0.1) 56%, transparent 74%); + background: radial-gradient( + circle at center, + rgba(253, 98, 22, 0.32), + rgba(253, 98, 22, 0.1) 56%, + transparent 74% + ); filter: blur(12px); pointer-events: none; animation: roleOrangeDrift 9s ease-in-out infinite; @@ -2544,7 +2597,6 @@ body { } } - .role-copy-list { margin: 12px 0 0; padding-left: 18px; @@ -2791,20 +2843,29 @@ body { font-size: 14px; font-weight: 600; text-decoration: none; - transition: transform 180ms ease, border-color 180ms ease, box-shadow 220ms ease, background-color 220ms ease; + transition: + transform 180ms ease, + border-color 180ms ease, + box-shadow 220ms ease, + background-color 220ms ease; } .lp-primary-btn { border: 1px solid rgba(253, 98, 22, 0.72); background: #fd6116; color: #fff; - box-shadow: 0 0 0 1px rgba(253, 98, 22, 0.4) inset, 0 12px 24px -16px rgba(253, 98, 22, 0.9); + box-shadow: + 0 0 0 1px rgba(253, 98, 22, 0.4) inset, + 0 12px 24px -16px rgba(253, 98, 22, 0.9); } .lp-primary-btn:hover { transform: translateY(-1px); background: #eb5b14; - box-shadow: 0 0 0 1px rgba(253, 98, 22, 0.6) inset, 0 0 0 4px rgba(253, 98, 22, 0.16), 0 18px 30px -16px rgba(253, 98, 22, 0.95); + box-shadow: + 0 0 0 1px rgba(253, 98, 22, 0.6) inset, + 0 0 0 4px rgba(253, 98, 22, 0.16), + 0 18px 30px -16px rgba(253, 98, 22, 0.95); } .lp-ghost-btn { @@ -2818,7 +2879,9 @@ body { .lp-ghost-btn:hover { transform: translateY(-1px); border-color: rgba(253, 98, 22, 0.7); - box-shadow: 0 0 0 3px rgba(253, 98, 22, 0.14), 0 14px 24px -14px rgba(2, 6, 23, 0.78); + box-shadow: + 0 0 0 3px rgba(253, 98, 22, 0.14), + 0 14px 24px -14px rgba(2, 6, 23, 0.78); } .lp-ghost-btn-dark { @@ -2833,7 +2896,7 @@ body { .lp-primary-btn::before, .lp-ghost-btn::before { - content: ''; + content: ""; position: absolute; inset: -160% auto -160% -35%; width: 45%; @@ -2960,7 +3023,11 @@ body { font-size: 11px; font-weight: 700; letter-spacing: 0.01em; - transition: opacity 320ms ease, border-color 280ms ease, background 280ms ease, box-shadow 280ms ease; + transition: + opacity 320ms ease, + border-color 280ms ease, + background 280ms ease, + box-shadow 280ms ease; animation: none; } @@ -2976,18 +3043,39 @@ body { letter-spacing: 0.01em; border: 1px solid rgba(255, 255, 255, 0.22); backdrop-filter: blur(8px); - transition: left 860ms ease, top 860ms ease, opacity 300ms ease, transform 300ms ease, border-color 280ms ease, background 280ms ease, box-shadow 280ms ease; + transition: + left 860ms ease, + top 860ms ease, + opacity 300ms ease, + transform 300ms ease, + border-color 280ms ease, + background 280ms ease, + box-shadow 280ms ease; } -.op-graph-node-profile { color: #fff; background: rgba(15, 23, 42, 0.6); } -.op-graph-node-opportunity { color: #fff; background: rgba(30, 41, 59, 0.5); } -.op-graph-node-signal { color: #fff; background: rgba(15, 23, 42, 0.48); } -.op-graph-node-status { color: #fff; background: rgba(51, 65, 85, 0.5); } +.op-graph-node-profile { + color: #fff; + background: rgba(15, 23, 42, 0.6); +} +.op-graph-node-opportunity { + color: #fff; + background: rgba(30, 41, 59, 0.5); +} +.op-graph-node-signal { + color: #fff; + background: rgba(15, 23, 42, 0.48); +} +.op-graph-node-status { + color: #fff; + background: rgba(51, 65, 85, 0.5); +} .op-graph-node-on { opacity: 1; border-color: rgba(253, 98, 22, 0.66); - box-shadow: 0 0 0 1px rgba(253, 98, 22, 0.14), 0 0 12px rgba(253, 98, 22, 0.34); + box-shadow: + 0 0 0 1px rgba(253, 98, 22, 0.14), + 0 0 12px rgba(253, 98, 22, 0.34); animation: opGraphNodeIn 260ms ease; } @@ -3003,7 +3091,9 @@ body { gap: 8px; opacity: 0; transform: scale(0.9); - transition: opacity 700ms ease, transform 700ms ease; + transition: + opacity 700ms ease, + transform 700ms ease; z-index: 5; overflow: hidden; } @@ -3089,7 +3179,9 @@ body { .op-graph-phase4 .op-graph-workspace { opacity: 1; transform: scale(1); - box-shadow: 0 0 0 1px rgba(253, 98, 22, 0.3), 0 0 22px rgba(253, 98, 22, 0.35); + box-shadow: + 0 0 0 1px rgba(253, 98, 22, 0.3), + 0 0 22px rgba(253, 98, 22, 0.35); } .op-graph-phase4 .op-graph-line, @@ -3243,7 +3335,10 @@ body { align-items: center; justify-content: center; color: #ff8f5a; - transition: transform 180ms ease, border-color 180ms ease, background 180ms ease; + transition: + transform 180ms ease, + border-color 180ms ease, + background 180ms ease; } .whyOrbitalBtn:hover { @@ -3299,7 +3394,10 @@ body { background: rgba(15, 23, 42, 0.35); color: #fff; font-size: 13px; - transition: transform 180ms ease, border-color 180ms ease, background 180ms ease; + transition: + transform 180ms ease, + border-color 180ms ease, + background 180ms ease; } .whyControlBtn:hover { @@ -3339,7 +3437,7 @@ body { } .hiwCodeBigRect::before { - content: ''; + content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(15, 23, 42, 0.08), rgba(15, 23, 42, 0.3)); @@ -3416,7 +3514,10 @@ body { display: grid; grid-template-columns: 30px 1fr; gap: 10px; - transition: border-color 180ms ease, transform 180ms ease, box-shadow 180ms ease; + transition: + border-color 180ms ease, + transform 180ms ease, + box-shadow 180ms ease; } .hiwCodeStep:hover { @@ -3482,7 +3583,10 @@ body { color: #1e293b; font-size: 16px; line-height: 1; - transition: border-color 180ms ease, transform 180ms ease, background-color 180ms ease; + transition: + border-color 180ms ease, + transform 180ms ease, + background-color 180ms ease; } .hiwCodeArrow:hover { @@ -3501,7 +3605,9 @@ body { height: 8px; border-radius: 999px; background: #cbd5e1; - transition: width 180ms ease, background-color 180ms ease; + transition: + width 180ms ease, + background-color 180ms ease; } .hiwCodeDotActive { @@ -3540,13 +3646,23 @@ body { } @keyframes whyHaloDriftA { - 0%, 100% { transform: translate3d(0, 0, 0); } - 50% { transform: translate3d(14px, 12px, 0); } + 0%, + 100% { + transform: translate3d(0, 0, 0); + } + 50% { + transform: translate3d(14px, 12px, 0); + } } @keyframes whyHaloDriftB { - 0%, 100% { transform: translate3d(0, 0, 0); } - 50% { transform: translate3d(-12px, -10px, 0); } + 0%, + 100% { + transform: translate3d(0, 0, 0); + } + 50% { + transform: translate3d(-12px, -10px, 0); + } } @keyframes hiwCodeCardIn { @@ -3697,10 +3813,14 @@ body { @keyframes ctaPulse { 0%, 100% { - box-shadow: 0 0 0 1px rgba(253, 98, 22, 0.4) inset, 0 12px 24px -16px rgba(253, 98, 22, 0.9); + box-shadow: + 0 0 0 1px rgba(253, 98, 22, 0.4) inset, + 0 12px 24px -16px rgba(253, 98, 22, 0.9); } 50% { - box-shadow: 0 0 0 1px rgba(253, 98, 22, 0.52) inset, 0 16px 28px -16px rgba(253, 98, 22, 0.98); + box-shadow: + 0 0 0 1px rgba(253, 98, 22, 0.52) inset, + 0 16px 28px -16px rgba(253, 98, 22, 0.98); } } @@ -3806,7 +3926,7 @@ body { margin: 0 auto; min-height: 100vh; display: grid; - align-items: center; + align-items: start; grid-template-columns: 1.02fr 0.98fr; gap: 24px; padding: 24px 0; @@ -3880,7 +4000,11 @@ body { gap: 8px; text-align: left; cursor: pointer; - transition: border-color 160ms ease, background-color 160ms ease, color 160ms ease, box-shadow 160ms ease; + transition: + border-color 160ms ease, + background-color 160ms ease, + color 160ms ease, + box-shadow 160ms ease; } .multi-select-option:hover { @@ -4053,7 +4177,9 @@ body { background: #fff; padding: 0 14px; font-size: 14px; - transition: border-color 180ms ease, box-shadow 180ms ease; + transition: + border-color 180ms ease, + box-shadow 180ms ease; } .auth-form .textarea { @@ -4410,7 +4536,7 @@ body { } .about-hero::before { - content: ''; + content: ""; position: absolute; inset: 0; background: @@ -4488,7 +4614,12 @@ body { position: absolute; inset: 0; pointer-events: none; - background: linear-gradient(110deg, transparent 22%, rgba(255, 255, 255, 0.18) 46%, transparent 68%); + background: linear-gradient( + 110deg, + transparent 22%, + rgba(255, 255, 255, 0.18) 46%, + transparent 68% + ); transform: translateX(-110%); animation: aboutSheenSweep 9s ease-in-out infinite; } @@ -4578,7 +4709,12 @@ body { height: min(340px, 46vh); transform: translate(-50%, -50%); border-radius: 999px; - background: radial-gradient(circle, rgba(253, 98, 22, 0.52), rgba(253, 98, 22, 0.08) 58%, transparent 76%); + background: radial-gradient( + circle, + rgba(253, 98, 22, 0.52), + rgba(253, 98, 22, 0.08) 58%, + transparent 76% + ); filter: blur(26px); pointer-events: none; transition: opacity 280ms ease; @@ -4594,7 +4730,9 @@ body { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(12px); pointer-events: none; - transition: opacity 320ms ease, transform 360ms ease; + transition: + opacity 320ms ease, + transform 360ms ease; } .about-problem-shape-a { @@ -4639,7 +4777,9 @@ body { font-weight: 800; letter-spacing: -0.02em; color: #fff; - transition: opacity 320ms ease, filter 320ms ease; + transition: + opacity 320ms ease, + filter 320ms ease; } .about-problem-body { @@ -4650,7 +4790,9 @@ body { font-size: clamp(16px, 2vw, 24px); line-height: 1.42; color: rgba(255, 255, 255, 0.86); - transition: opacity 320ms ease, filter 320ms ease; + transition: + opacity 320ms ease, + filter 320ms ease; } .about-glass-light { @@ -4740,7 +4882,12 @@ body { .about-chapter-two-reflection { position: absolute; inset: 0; - background: linear-gradient(110deg, transparent 25%, rgba(253, 98, 22, 0.15) 48%, transparent 72%); + background: linear-gradient( + 110deg, + transparent 25%, + rgba(253, 98, 22, 0.15) 48%, + transparent 72% + ); transform: translateX(-110%); animation: chapterTwoSweep 6s ease-out infinite; } @@ -4787,7 +4934,9 @@ body { color: #334155; font-size: 14px; font-weight: 500; - transition: opacity 420ms ease-out, transform 420ms ease-out; + transition: + opacity 420ms ease-out, + transform 420ms ease-out; } .about-chapter-two-row-dot { @@ -4832,7 +4981,10 @@ body { background: rgba(255, 255, 255, 0.09); padding: 14px; backdrop-filter: blur(12px); - transition: transform 260ms ease, opacity 260ms ease, box-shadow 260ms ease; + transition: + transform 260ms ease, + opacity 260ms ease, + box-shadow 260ms ease; } .about-trust-sequence-card:hover { @@ -4900,7 +5052,7 @@ body { } .about-principle-narrative-section::before { - content: ''; + content: ""; position: absolute; inset: 0; border-radius: 24px; @@ -4934,9 +5086,16 @@ body { height: clamp(180px, 32vh, 300px); transform: translate(-50%, -50%); border-radius: 999px; - background: radial-gradient(circle, rgba(253, 98, 22, 0.66), rgba(253, 98, 22, 0.2) 50%, transparent 72%); + background: radial-gradient( + circle, + rgba(253, 98, 22, 0.66), + rgba(253, 98, 22, 0.2) 50%, + transparent 72% + ); filter: blur(30px); - transition: opacity 260ms ease, transform 260ms ease; + transition: + opacity 260ms ease, + transform 260ms ease; pointer-events: none; } @@ -4949,7 +5108,10 @@ body { .about-narrative-item-active, .about-narrative-item-inactive { - transition: opacity 260ms ease, transform 260ms ease, text-shadow 260ms ease; + transition: + opacity 260ms ease, + transform 260ms ease, + text-shadow 260ms ease; } .about-narrative-item-active { @@ -5072,7 +5234,10 @@ body { opacity: 0; transform: translate3d(0, 12px, 0); clip-path: inset(0 0 100% 0 round 20px); - transition: opacity 520ms ease, transform 520ms ease, clip-path 620ms ease; + transition: + opacity 520ms ease, + transform 520ms ease, + clip-path 620ms ease; } .about-timeline-mask-show { @@ -5110,7 +5275,11 @@ body { padding: 14px; opacity: 0.12; transform: translate3d(0, 12px, 0) scale(0.985); - transition: border-color 220ms ease, box-shadow 220ms ease, transform 420ms ease, opacity 420ms ease; + transition: + border-color 220ms ease, + box-shadow 220ms ease, + transform 420ms ease, + opacity 420ms ease; } .about-timeline-milestone-visible { @@ -5172,7 +5341,9 @@ body { .about-reveal-init { opacity: 0; transform: translate3d(0, 14px, 0); - transition: opacity 420ms ease, transform 420ms ease; + transition: + opacity 420ms ease, + transform 420ms ease; } .about-reveal-show { @@ -5200,13 +5371,23 @@ body { } @keyframes problemFloatOne { - 0%, 100% { transform: translate3d(0, 0, 0); } - 50% { transform: translate3d(0, -10px, 0); } + 0%, + 100% { + transform: translate3d(0, 0, 0); + } + 50% { + transform: translate3d(0, -10px, 0); + } } @keyframes problemFloatTwo { - 0%, 100% { transform: translate3d(0, 0, 0); } - 50% { transform: translate3d(0, 8px, 0); } + 0%, + 100% { + transform: translate3d(0, 0, 0); + } + 50% { + transform: translate3d(0, 8px, 0); + } } @media (min-width: 640px) { @@ -5378,7 +5559,9 @@ body { font-size: 14px; font-weight: 500; text-decoration: none; - transition: background 150ms, color 150ms; + transition: + background 150ms, + color 150ms; cursor: pointer; border: none; background: transparent; @@ -5447,7 +5630,9 @@ body { border-radius: 8px; color: #475569; text-decoration: none; - transition: background 150ms, color 150ms; + transition: + background 150ms, + color 150ms; } .topbar-icon-btn:hover { @@ -5527,7 +5712,9 @@ body { color: #991b1b; } -.status-banner span { font-size: 22px; } +.status-banner span { + font-size: 22px; +} .status-banner strong { display: block; @@ -5535,7 +5722,11 @@ body { margin-bottom: 2px; } -.status-banner p { margin: 0; font-size: 13px; opacity: 0.85; } +.status-banner p { + margin: 0; + font-size: 13px; + opacity: 0.85; +} /* ── KPI Cards ── */ .kpi-grid { @@ -5556,7 +5747,9 @@ body { transition: box-shadow 200ms; } -.kpi-card:hover { box-shadow: 0 8px 24px -12px rgba(2, 6, 23, 0.18); } +.kpi-card:hover { + box-shadow: 0 8px 24px -12px rgba(2, 6, 23, 0.18); +} .kpi-icon { width: 44px; @@ -5568,9 +5761,15 @@ body { font-size: 20px; } -.kpi-icon--blue { background: #eff6ff; } -.kpi-icon--green { background: #f0fdf4; } -.kpi-icon--orange { background: #fff7ed; } +.kpi-icon--blue { + background: #eff6ff; +} +.kpi-icon--green { + background: #f0fdf4; +} +.kpi-icon--orange { + background: #fff7ed; +} .kpi-value { font-size: 28px; @@ -5768,7 +5967,9 @@ body { .role-path-card.selected { border-color: #fd6116; - box-shadow: 0 0 0 2px rgba(253, 98, 22, 0.28), 0 22px 42px -30px rgba(253, 98, 22, 0.85); + box-shadow: + 0 0 0 2px rgba(253, 98, 22, 0.28), + 0 22px 42px -30px rgba(253, 98, 22, 0.85); } .role-path-card .path-chip { @@ -5808,7 +6009,7 @@ body { } .role-card::before { - content: ''; + content: ""; position: absolute; inset: -1px; border-radius: 18px; @@ -5833,7 +6034,9 @@ body { .role-card.selected { border-color: #fd6116; background: #ffffff; - box-shadow: 0 0 0 2px rgba(253, 98, 22, 0.3) inset, 0 24px 52px -20px rgba(253, 98, 22, 0.3); + box-shadow: + 0 0 0 2px rgba(253, 98, 22, 0.3) inset, + 0 24px 52px -20px rgba(253, 98, 22, 0.3); } .role-card:disabled { @@ -5929,9 +6132,21 @@ body { gap: 16px; } -.pending-icon { font-size: 56px; } -.pending-container h1 { margin: 0; font-size: 24px; font-weight: 800; color: #100b2f; } -.pending-container p { margin: 0; color: #64748b; font-size: 15px; line-height: 1.6; } +.pending-icon { + font-size: 56px; +} +.pending-container h1 { + margin: 0; + font-size: 24px; + font-weight: 800; + color: #100b2f; +} +.pending-container p { + margin: 0; + color: #64748b; + font-size: 15px; + line-height: 1.6; +} .pending-request-box, .pending-reason-box { @@ -5945,8 +6160,13 @@ body { text-align: left; } -.pending-support { font-size: 13px; color: #94a3b8; } -.pending-support a { color: #fd6116; } +.pending-support { + font-size: 13px; + color: #94a3b8; +} +.pending-support a { + color: #fd6116; +} /* ── Shared Button Variants ── */ .btn-primary { @@ -5954,14 +6174,20 @@ body { border-color: var(--brand-orange); color: #fff; } -.btn-primary:hover { background: var(--brand-orange-dark); } +.btn-primary:hover { + background: var(--brand-orange-dark); +} .btn-outline { background: transparent; border-color: currentColor; } -.btn-sm { padding: 6px 12px; font-size: 13px; border-radius: 8px; } +.btn-sm { + padding: 6px 12px; + font-size: 13px; + border-radius: 8px; +} /* ── Error Banner ── */ .error-banner { @@ -5998,7 +6224,9 @@ body { color: #100b2f; } -.data-table tr:hover td { background: #f8fafc; } +.data-table tr:hover td { + background: #f8fafc; +} /* ── Status Badges ── */ .badge { @@ -6010,11 +6238,26 @@ body { letter-spacing: 0.04em; } -.badge--green { background: #dcfce7; color: #15803d; } -.badge--orange { background: #fff7ed; color: #c2410c; } -.badge--blue { background: #eff6ff; color: #1d4ed8; } -.badge--red { background: #fff1f2; color: #be123c; } -.badge--gray { background: #f1f5f9; color: #475569; } +.badge--green { + background: #dcfce7; + color: #15803d; +} +.badge--orange { + background: #fff7ed; + color: #c2410c; +} +.badge--blue { + background: #eff6ff; + color: #1d4ed8; +} +.badge--red { + background: #fff1f2; + color: #be123c; +} +.badge--gray { + background: #f1f5f9; + color: #475569; +} /* ── Page layout helpers ── */ .page-actions { @@ -6042,10 +6285,13 @@ body { font-weight: 600; color: #475569; cursor: pointer; - transition: border-color 150ms, background 150ms; + transition: + border-color 150ms, + background 150ms; } -.filter-btn.active, .filter-btn:hover { +.filter-btn.active, +.filter-btn:hover { border-color: #fd6116; color: #fd6116; background: #fff7ed; @@ -6104,7 +6350,9 @@ body { text-transform: uppercase; letter-spacing: 0.05em; } -.input, .textarea, .select { +.input, +.textarea, +.select { width: 100%; padding: 10px 14px; border: 1.5px solid #e2e8f0; @@ -6113,14 +6361,19 @@ body { font-family: inherit; font-size: 14px; color: #1e293b; - transition: border-color 150ms, box-shadow 150ms; + transition: + border-color 150ms, + box-shadow 150ms; outline: none; } -.input:focus, .textarea:focus, .select:focus { +.input:focus, +.textarea:focus, +.select:focus { border-color: #fd6116; box-shadow: 0 0 0 3px rgba(253, 98, 22, 0.12); } -.input::placeholder, .textarea::placeholder { +.input::placeholder, +.textarea::placeholder { color: #94a3b8; } .textarea { @@ -6171,7 +6424,9 @@ body { font-weight: 600; transition: color 150ms; } -.back-link a:hover { color: #fd6116; } +.back-link a:hover { + color: #fd6116; +} /* ── Button Sizes ────────────────────────────────────────────────────────── */ .btn-sm { @@ -6217,7 +6472,10 @@ body { border: 1px solid #bbf7d0; color: #15803d; } -.status-banner p { margin: 4px 0 0; font-size: 13px; } +.status-banner p { + margin: 4px 0 0; + font-size: 13px; +} /* ── Badges ──────────────────────────────────────────────────────────────── */ .badge { @@ -6230,11 +6488,26 @@ body { letter-spacing: 0.03em; text-transform: uppercase; } -.badge--gray { background: #f1f5f9; color: #475569; } -.badge--blue { background: #eff6ff; color: #1d4ed8; } -.badge--green { background: #f0fdf4; color: #15803d; } -.badge--orange { background: #fff7ed; color: #c2410c; } -.badge--red { background: #fef2f2; color: #b91c1c; } +.badge--gray { + background: #f1f5f9; + color: #475569; +} +.badge--blue { + background: #eff6ff; + color: #1d4ed8; +} +.badge--green { + background: #f0fdf4; + color: #15803d; +} +.badge--orange { + background: #fff7ed; + color: #c2410c; +} +.badge--red { + background: #fef2f2; + color: #b91c1c; +} /* ── Buttons ─────────────────────────────────────────────────────────────── */ .btn { @@ -6252,7 +6525,11 @@ body { font-weight: 700; cursor: pointer; text-decoration: none; - transition: border-color 150ms, background 150ms, color 150ms, box-shadow 150ms; + transition: + border-color 150ms, + background 150ms, + color 150ms, + box-shadow 150ms; white-space: nowrap; } .btn:hover:not(:disabled) { @@ -6389,7 +6666,7 @@ body { } .activity-timeline::before { - content: ''; + content: ""; position: absolute; left: 3px; top: 8px; diff --git a/test-videos/5963f99d7049457dc44031f8de1b036e.webm b/test-videos/5963f99d7049457dc44031f8de1b036e.webm new file mode 100644 index 0000000..f6fbbe7 Binary files /dev/null and b/test-videos/5963f99d7049457dc44031f8de1b036e.webm differ diff --git a/test-videos/b1138b33a3072368fbf41afe386d65bf.webm b/test-videos/b1138b33a3072368fbf41afe386d65bf.webm new file mode 100644 index 0000000..e62a8b4 Binary files /dev/null and b/test-videos/b1138b33a3072368fbf41afe386d65bf.webm differ diff --git a/tests/e2e/company-verification-flow.spec.ts b/tests/e2e/company-verification-flow.spec.ts new file mode 100644 index 0000000..c589592 --- /dev/null +++ b/tests/e2e/company-verification-flow.spec.ts @@ -0,0 +1,212 @@ +import { test, expect, chromium } from "@playwright/test"; +import { randomUUID } from "crypto"; + +// Generate random test data +const testEmail = `testcompany${randomUUID().slice(0, 8)}@test.com`; +const testPassword = "TestPassword123!"; +const testCompanyName = `Test Company ${randomUUID().slice(0, 6)}`; + +console.log("πŸ§ͺ Starting E2E Test Flow"); +console.log("πŸ“§ Test Email:", testEmail); +console.log("🏒 Company Name:", testCompanyName); + +test.setTimeout(120000); + +test("Company signup -> verification flow", async () => { + // Launch browser with UI visible + const browser = await chromium.launch({ + headless: false, + slowMo: 500, // Slow down for visibility + }); + + const context = await browser.newContext({ + viewport: { width: 1400, height: 900 }, + recordVideo: { + dir: "./test-videos/", + size: { width: 1400, height: 900 }, + }, + }); + + const page = await context.newPage(); + + try { + // Step 1: Navigate to public website signup + console.log("🌐 Step 1: Opening public website..."); + await page.goto("http://localhost:3001/signup"); + await page.waitForLoadState("networkidle"); + + // Take screenshot of signup page + await page.screenshot({ path: "./test-results/01-signup-page.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Signup page"); + + // Step 2: Fill signup form + console.log("✍️ Step 2: Filling signup form..."); + await page.fill('input[name="email"], input[type="email"]', testEmail); + await page.fill('input[name="password"], input[type="password"]', testPassword); + await page.fill('input[name="confirmPassword"], input[name="confirm"]', testPassword); + + await page.screenshot({ path: "./test-results/02-signup-filled.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Signup form filled"); + + // Click signup button + await page.click('button[type="submit"], button:has-text("Sign")'); + await page.waitForTimeout(3000); + + await page.screenshot({ path: "./test-results/03-after-signup.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: After signup"); + + // Step 3: Select Company role + console.log("🎯 Step 3: Selecting Company role..."); + await page.waitForSelector("text=Company, text=company", { timeout: 10000 }); + await page.click("text=Company"); + await page.waitForTimeout(2000); + + await page.screenshot({ path: "./test-results/04-company-selected.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Company role selected"); + + // Step 4: Fill Company Profile + console.log("πŸ“ Step 4: Filling company profile..."); + + // Company name + await page.fill( + 'input[name="companyName"], input[name="name"], input[placeholder*="company"]', + testCompanyName + ); + + // Business type + await page.selectOption('select[name="businessType"]', "Private Limited"); + + // Industry + await page.fill('input[name="industry"]', "Technology"); + + // Website + await page.fill('input[name="website"]', "https://testcompany.com"); + + // Description + await page.fill( + 'textarea[name="description"], textarea[name="about"]', + "We are a technology company looking for talented professionals to join our team." + ); + + await page.screenshot({ path: "./test-results/05-profile-filled.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Profile details filled"); + + // Contact details + await page.fill('input[name="contactName"]', "John Doe"); + await page.fill('input[name="contactEmail"]', testEmail); + await page.fill('input[name="contactPhone"]', "+91 9876543210"); + + // Address + await page.fill('input[name="address"], textarea[name="address"]', "123 Tech Park, Bangalore"); + await page.fill('input[name="city"]', "Bangalore"); + await page.fill('input[name="state"]', "Karnataka"); + await page.fill('input[name="country"]', "India"); + await page.fill('input[name="postalCode"]', "560001"); + + await page.screenshot({ path: "./test-results/06-contact-filled.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Contact details filled"); + + // Step 5: Submit for verification + console.log("πŸš€ Step 5: Submitting for verification..."); + await page.click('button[type="submit"], button:has-text("Submit"), button:has-text("Save")'); + await page.waitForTimeout(3000); + + await page.screenshot({ path: "./test-results/07-profile-submitted.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Profile submitted"); + + // Wait for verification status page + await page.waitForSelector("text=verification, text=Verification, text=status", { + timeout: 10000, + }); + + await page.screenshot({ path: "./test-results/08-verification-status.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Verification status page"); + + // Step 6: Open Admin Panel + console.log("πŸ” Step 6: Opening admin panel..."); + const adminPage = await context.newPage(); + await adminPage.goto("http://localhost:3000/login"); + await adminPage.waitForLoadState("networkidle"); + + await adminPage.screenshot({ path: "./test-results/09-admin-login.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Admin login page"); + + // Admin login (assuming default admin credentials) + // Note: You may need to adjust these credentials + await adminPage.fill('input[name="email"], input[type="email"]', "admin@nxtgauge.com"); + await adminPage.fill('input[name="password"], input[type="password"]', "admin123"); + + await adminPage.click( + 'button[type="submit"], button:has-text("Login"), button:has-text("Sign In")' + ); + await adminPage.waitForTimeout(3000); + + await adminPage.screenshot({ path: "./test-results/10-admin-logged-in.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Admin logged in"); + + // Step 7: Navigate to Verification Management + console.log("πŸ” Step 7: Navigating to Verification Management..."); + + // Click on Verifications in sidebar + await adminPage.click( + 'text=Verifications, a:has-text("Verifications"), [href*="verification"]' + ); + await adminPage.waitForTimeout(3000); + + await adminPage.screenshot({ path: "./test-results/11-verification-list.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Verification list page"); + + // Step 8: Check if our company appears in verification queue + console.log("πŸ”Ž Step 8: Checking for company verification request..."); + + // Search for the company name + await adminPage.fill('input[placeholder*="search"], input[name="search"]', testCompanyName); + await adminPage.waitForTimeout(2000); + + await adminPage.screenshot({ path: "./test-results/12-search-results.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Search results"); + + // Check if company is found + const companyFound = await adminPage + .locator(`text=${testCompanyName}`) + .isVisible() + .catch(() => false); + + if (companyFound) { + console.log("βœ… SUCCESS: Company verification request found in admin panel!"); + + // Click on the company to view details + await adminPage.click(`text=${testCompanyName}`); + await adminPage.waitForTimeout(3000); + + await adminPage.screenshot({ path: "./test-results/13-company-details.png", fullPage: true }); + console.log("πŸ“Έ Screenshot: Company verification details"); + + // Verify the details match what we submitted + await expect(adminPage.locator("body")).toContainText(testEmail); + await expect(adminPage.locator("body")).toContainText("Bangalore"); + + console.log("βœ… All verification details match!"); + } else { + console.log("⚠️ Company not found in verification queue"); + console.log(" This might be because:"); + console.log(" - The profile is still being processed"); + console.log(" - The verification queue filters are different"); + console.log(" - The admin credentials are incorrect"); + } + + // Keep browser open for 10 seconds to show results + console.log("⏳ Keeping browser open for 10 seconds..."); + await page.waitForTimeout(10000); + } catch (error) { + console.error("❌ Test failed:", error); + await page.screenshot({ path: "./test-results/error-screenshot.png", fullPage: true }); + throw error; + } finally { + await context.close(); + await browser.close(); + console.log("βœ… Test completed!"); + console.log("πŸ“Ή Video saved to: ./test-videos/"); + console.log("πŸ“Έ Screenshots saved to: ./test-results/"); + } +});