The Markdown we wrote for this page is displayed below. We then generated the HTML using a static site generator and our in-house code. Your browser then uses our Style Sheets and some Javascript to beautifully render the HTML. Please click the button to restore the page.
---
title: ""
description: "Purchase Your QuantaLumin Website"
date: ""
draft: false
params:
list: false
summaries: false
sections:
- section: "wrapper style3 content-align-center pad"
---
<div id="payment-table"></div>
<!-- Loading Spinner -->
<div id="loading-spinner" style="display: none;">
<div class="spinner"></div>
<p id="loading-text">QuantaLumin Partners with Stripe...</p>
</div>
<style>
/* Loading Spinner Style */
#loading-spinner {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 1000;
background-color: rgba(0, 0, 0, 0.7);
padding: 20px;
border-radius: 8px;
color: white;
font-size: 1.2rem;
display: none;
}
.spinner {
border: 5px solid #000000;
border-top: 5px solid #a3ff98;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 2s linear infinite;
}
#loading-text {
color: #ffffff;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Text Color Animation */
#loading-text {
}
}
/* Payment Table Container */
#payment-table {
position: relative;
overflow: visible;
}
/* ---------- Billing Controls Layout Refinement ---------- */
.billing-controls {
max-width: 800px;
margin: 1rem auto;
padding: 1rem;
background: none;
box-shadow: none;
border-radius: 0;
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Currency and Discount Inputs Container (moved to top) */
.billing-extra {
display: flex;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
}
.currency-select,
.discount-input {
flex: 1;
display: flex;
align-items: center;
gap: 0.5rem;
color: white;
}
.currency-select label,
.discount-input label {
font-size: 0.9rem;
font-weight: bold;
color: white;
}
.currency-select select,
.discount-input input {
padding: 0.4rem;
border: 1px solid #ccc;
border-radius: 4px;
flex: 1;
color: white;
}
/* Billing Courses Button (separate and wide) */
.billing-courses {
display: flex;
justify-content: center;
width: 100%;
}
.billing-courses .plan-button {
width: 100%;
background: black;
color: white;
border: none;
padding: 0.5rem 1rem;
cursor: pointer;
background-image: linear-gradient(100deg, #000, #fff 50%, #000);
background-size: 400% 200%;
animation: shimmer 7s infinite;
}
/* Billing Toggle now contains only the websites buttons */
.billing-toggle {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.billing-websites {
color: black;
display: flex;
align-items: center;
gap: 0.5rem;
overflow-x: auto;
-webkit-overflow-scrolling: touch; /* Enables smooth scrolling on iOS */
}
.billing-icon {
font-size: 1.2rem;
margin-right: 0.5rem;
}
.billing-toggle button {
padding: 0.5rem 1rem;
border: 1px solid #ccc;
background: white;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s;
}
.billing-toggle button.active {
background: #333;
color: white;
border-color: white;
}
/* Navbar styling in header-divider */
.header-divider {
overflow: visible; /* Ensure submenu is not clipped */
}
.header-divider {
width: 100%;
padding: 0.5rem 2.1rem;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: height 0.3s ease;
z-index: 10;
display: flex;
align-items: center;
justify-content: space-between;
}
#navbar ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
gap: 1rem;
}
#navbar ul li {
position: relative;
}
#navbar ul li a {
display: block; /* Make the whole li clickable */
text-decoration: none; /* Remove underlines */
padding: 0.5rem 1rem;
color: inherit;
transition: background 0.2s;
}
#navbar ul li a:hover {
background-color: #f0f0f0;
border-radius: 4px;
}
/* Ensure submenu styling */
.submenu {
display: none;
position: absolute;
top: calc(100% - 5px);
left: 0;
background: white;
border: 1px solid #ccc;
padding: 0.5rem;
white-space: nowrap;
z-index: 10000;
border-radius: 4px;
}
.submenu li {
display: block;
margin: 0;
}
.submenu li a {
padding: 0.3rem 0.8rem;
color: black;
}
/* On mobile, make header-divider split vertically */
@media (max-width: 600px) {
.header-divider {
flex-direction: column;
align-items: stretch;
height: auto;
text-align: center;
padding: 1rem 0;
}
.header-left, .header-center, .header-right {
width: 100%;
justify-content: center;
}
#navbar {
z-index: 10000;
}
#navbar ul {
flex-direction: column;
align-items: top;
}
#navbar ul li {
width: 100%;
}
.submenu {
position: absolute;
top: 100%;
width: 100%;
left: 0;
text-align: center;
}
}
.has-submenu {
cursor: pointer;
position: relative;
}
.has-submenu:hover {
background-color: #f0f0f0;
}
/* ---------- Swipe Animations for Billing Buttons ---------- */
@keyframes slideOut {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(-100%); opacity: 0; }
}
.slide-out { animation: slideOut 0.3s forwards; }
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.slide-in { animation: slideIn 0.3s forwards; }
/* Discount Input Box Styles */
.discount-input {
margin: 0.5rem;
}
.discount-input input {
padding: 0.4rem;
border: 1px solid #ccc;
}
/* ---------- Orbital & Nucleus Animation ---------- */
.orbit-rotation #orbital-1 {
animation: rotateOrbit1 20s linear infinite;
transform-origin: 0 0;
}
.orbit-rotation #orbital-2 {
animation: rotateOrbit2 25s linear infinite;
transform-origin: 0 0;
}
.orbit-rotation #orbital-3 {
animation: rotateOrbit3 30s linear infinite;
transform-origin: 0 0;
}
.orbit-rotation #orbital-4 {
animation: rotateOrbit4 35s linear infinite;
transform-origin: 0 0;
}
@keyframes rotateOrbit1 { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes rotateOrbit2 { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
@keyframes rotateOrbit3 { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes rotateOrbit4 { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
/* Nucleus & Glow */
.nucleus-filled { fill: #a3ff98 !important; }
@keyframes nucleusFlash {
0% { stroke: none; stroke-width: 0; }
30% { stroke: yellow; stroke-width: 4; }
60% { stroke: yellow; stroke-width: 4; }
100% { stroke: none; stroke-width: 0; }
}
.nucleus-flash { animation: nucleusFlash 1s ease; }
.card-glow { box-shadow: 0 0 40px 8px #a3ff98 !important; animation: nucleusFlash 1s ease; }
/* Discount Applied Sticker */
.discount-sticker {
position: absolute;
top: -10px;
right: -10px;
background: linear-gradient(120deg, gold, orange);
color: #333;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
overflow: hidden;
}
/* ---------- Fade & Layout ---------- */
.fade-in { animation: fadeIn 1s forwards; }
.fade-out { animation: fadeOut 1s forwards; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
.billing-controls {
display: flex; justify-content: space-between; align-items: center;
flex-wrap: wrap; margin: 1rem auto; max-width: 800px; padding: 0 1rem;
position: relative; overflow: hidden;
}
.billing-toggle {
display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap;
flex: 1; position: static; transform: none;
}
.billing-toggle button {
padding: 0.4rem 0.8rem; border: 1px solid #ccc; background: white;
border-radius: 0; cursor: pointer; transition: background 0.2s;
flex-shrink: 1; white-space: nowrap;
}
.billing-toggle button.active {
background: black; color: white; border-color: black;
}
.currency-select {
margin-left: auto; display: flex; align-items: center; gap: 0.5rem;
font-size: 0.9rem; flex-shrink: 0;
}
.billing-four-months .short-text {
display: none;
}
/* On mobile, stack label on top of input */
@media (max-width: 600px) {
.currency-select,
.discount-input {
flex-direction: column;
align-items: stretch;
}
.currency-select label,
.discount-input label {
margin-bottom: 0.3rem;
}
.billing-four-months .full-text {
display: none;
}
.billing-four-months .short-text {
display: inline;
}
}
/* ---------- Plan Card Layout ---------- */
#plan-cards {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(249px, 1fr));
font-family: system-ui, sans-serif;
max-width: 95vw;
margin: auto;
padding: 1rem;
}
.plan-wrapper {
transform: translateY(30px);
background-color: white;
}
@keyframes fadeSlide { to { opacity: 1; transform: translateY(0); } }
.plan {
width: 100%; /* Now fills the entire plan-wrapper */
height: 100%;
border: 1px solid black;
border-radius: 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
position: relative;
transition: transform 0.2s;
display: flex;
flex-direction: column;
height: 100%; /* ensures card fills its container height */
}
.plan-title { font-size: 1.25rem; font-weight: bold; margin-bottom: 0.5rem; }
.plan-price { font-size: 1.5rem; color: black; margin-bottom: 1rem; }
.plan-price small { display: block; font-size: 0.75rem; color: #555; }
.plan-desc { font-size: 0.95rem; color: black; margin-bottom: 1rem; }
.plan-features {
list-style: none;
padding: 0;
margin: 1rem 0;
text-align: center; /* center text for features */
flex-grow: 1; /* take remaining space */
display: flex;
flex-direction: column;
align-items: center; /* center list items */
justify-content: center;
}
.plan-features li {
text-align: center;
}
.plan-features li::before { content: "✓ "; color: green; }
.plan-button {
background: black;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 0;
cursor: pointer;
font-size: 1rem;
margin-top: auto; /* pushes button to bottom */
align-self: center; /* Centers the button horizontally */
box-shadow: 8px 8px 10px rgba(0, 0, 0, 0.6);
}
.plan-button:hover { background: #333; }
.secondary-button {
background: black;
margin-left: 0.5rem;
border-radius: 0;
color: white;
padding: 0.5rem 1rem;
border: none;
cursor: pointer;
font-size: 1rem;
}
.secondary-button:hover { background: #333; }
.best-deal {
position: absolute;
top: -10px;
right: -10px;
background: linear-gradient(120deg, gold, orange);
color: #333;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 12px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
overflow: hidden;
}
/* ---------- Selection & Atom Styles ---------- */
.selection-container { padding: 0; font-family: system-ui, sans-serif; position: relative; }
/* ---------- Fixed Header & Footer ---------- */
.header-divider, .footer-divider {
width: 100%; padding: 1rem 2.1rem; background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: height 0.3s ease;
z-index: 10;
}
.header-divider { position: fixed; top: 0; left: 0; right: 0; height: 27vh; display: flex; align-items: center; }
.footer-divider {
position: fixed; bottom: 0; left: 0; right: 0; height: 8vh; display: flex; align-items: center; justify-content: flex-end;
box-sizing: border-box; background: #a3ff98;
}
.header-left, .header-center, .header-right {
flex: 1; display: flex; align-items: center; justify-content: center;
}
.header-center {
height: 100%;
}
.header-left { justify-content: flex-start; }
.header-right { justify-content: flex-end; }
#atom-svg {
width: auto; max-height: 200px; cursor: pointer;
}
@media (max-width: 600px) {
.header-divider, .footer-divider { padding: 1rem; }
.header-divider { height: 17vh; }
.footer-divider { height: 10vh; }
.body-divider { top: calc(18vh + 1rem); bottom: calc(10vh + 0.5rem); }
.header-left, .header-center, .header-right { justify-content: center; width: 100%; }
#atom-svg { width: 150px; height: 150px; }
/* Make navbar items stack vertically on mobile */
#navbar ul { flex-direction: column; align-items: flex-start; }
}
.back-button {
background: black; border: none; padding: 0.5rem 1rem;
border-radius: 0; cursor: pointer; font-size: 1rem; color: white;
}
.back-button:hover { background: #333; }
/* ---------- Scrolling Body Section ---------- */
.body-divider {
scroll-behavior: smooth;
position: fixed;
top: calc(27vh);
bottom: calc(8vh);
left: 0; right: 0;
overflow-y: auto;
padding-bottom: 1rem;
}
.discount-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-overflow: ellipsis;
white-space: nowrap;
margin: 0;
font-size: 24px;
font-weight: bold;
animation: tilt-n-move-shaking 8.3s linear infinite;
text-shadow: 4px 4px 7px rgba(0, 0, 0, 0.6);
}
@keyframes tilt-n-move-shaking {
/* Shake Segment 1 (0%–8%) */
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
4% {
transform: translate(calc(-50% + 5px), calc(-50% + 5px)) rotate(5deg);
}
8% {
transform: translate(-50%, -50%) rotate(0deg);
}
/* First Pause (8%–22%) */
22% {
transform: translate(-50%, -50%) rotate(0deg);
}
/* Shake Segment 2 (22%–30%) */
26% {
transform: translate(calc(-50% - 5px), calc(-50% + 5px)) rotate(-5deg);
}
30% {
transform: translate(-50%, -50%) rotate(0deg);
}
/* Second Pause (30%–55%) */
55% {
transform: translate(-50%, -50%) rotate(0deg);
}
/* Shake Segment 3 (55%–63%) */
59% {
transform: translate(calc(-50% + 5px), calc(-50% + 5px)) rotate(5deg);
}
63% {
transform: translate(-50%, -50%) rotate(0deg);
}
/* Third Pause (63%–75%) */
75% {
transform: translate(-50%, -50%) rotate(0deg);
}
/* Shake Segment 4 (75%–80%) */
77.5% {
transform: translate(calc(-50% - 5px), calc(-50% + 5px)) rotate(-5deg);
}
80% {
transform: translate(-50%, -50%) rotate(0deg);
}
/* Fourth Pause (80%–100%) */
100% {
transform: translate(-50%, -50%) rotate(0deg);
}
}
/* ---------- Selection Cards Styles ---------- */
.selection-section { margin-bottom: 2rem; }
.selection-section h2 { text-align: center; margin-bottom: 1rem; color: white; }
.selection-cards {
padding: 0 1rem; box-sizing: border-box;
display: grid; gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(249px, 1fr));
}
@keyframes swipeIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
.swipe-in { animation: swipeIn 0.5s forwards; }
.selection-card * { pointer-events: none; }
.selection-card {
z-index: 1; pointer-events: auto;
border: 1px solid black; border-radius: 16px; padding: 1rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.05); cursor: pointer;
transition: border 0.2s, box-shadow 0.2s, background-color 0.2s;
position: relative; width: 100%; box-sizing: border-box;
background: white;
}
.selection-card.selected {
background-color: transparent;
color: white;
border: 2px solid white;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.selection-card:hover:not(.selected) {
border: 4px solid #a3ff98;
}
.selection-card:hover:.selected {
border: 4px solid #fff;
}
.selection-card img { width: 100%; border-radius: 8px; margin-bottom: 0.5rem; }
.selection-card h3 { font-size: 1.1rem; margin: 0.5rem 0; }
.selection-card p { font-size: 0.9rem; margin: 0.5rem 0; color: white; }
.selection-card .price { font-size: 1.2rem; color: inherit; margin-bottom: 0.5rem; }
.electron-count { font-size: 0.85rem; color: #ccc; margin-bottom: 0.5rem; }
/* Quiz Options Styling – using divs for clarity */
.quiz-option {
background: white;
color: black;
border: 2px solid #333;
border-radius: 4px;
padding: 4px 8px; /* Reduced padding */
margin: 4px; /* Reduced margin */
font-size: 1rem; /* Smaller text */
text-align: center;
cursor: pointer;
min-width: 80px; /* Ensure they fit within the header */
}
.quiz-option:hover {
background: #eee;
}
.quiz-timer {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.quiz-question {
font-size: 0.8rem; /* Smaller text */
}
/* ---------- Electron & Photon Animations ---------- */
.flying-electron {
width: 10px; height: 10px; border-radius: 50%;
background: black; position: fixed; z-index: 2000;
border: 1px solid white;
pointer-events: none; transition: left 0.8s ease, top 0.8s ease;
}
.flying-photon {
position: fixed; z-index: 2000; pointer-events: none;
border: 1px solid white;
width: 36px; height: 18px; transition: left 0.8s ease, top 0.8s ease, opacity 0.8s ease;
}
/* Checkout Button Gradient */
.checkout-btn {
z-index: 10000;
background-size: cover;
color: white;
border-color: none;
background-color: black;
padding: 0rem 1.2rem;
cursor: pointer;
box-sizing: border-box;
white-space: nowrap;
flex: 0 0 auto;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
background-image: linear-gradient(100deg, #000, #fff 50%, #000);
background-size: 400% 200%;
animation: shimmer 7s infinite;
box-shadow: 8px 8px 10px rgba(0, 0, 0, 0.6);
}
@keyframes shimmer {
0% {
background-position: 0% 0%;
}
100% {
background-position: -275% 0%;
}
}
#starfield {
position: absolute;
top: 0;
left: 0;
z-index: -1; /* Behind the content */
pointer-events: none; /* Let clicks pass through to underlying elements */
width: 100%;
height: 200vh;
}
/* Default styling for the burger button and mobile navbar */
#burger-menu {
font-size: 1.5rem;
background: none;
border: none;
cursor: pointer;
display: none; /* hidden on desktop */
}
/* Mobile adjustments */
@media (max-width: 600px) {
/* Show the burger menu button */
#burger-menu {
display: block;
}
/* Hide the navbar by default */
#navbar {
display: none;
width: 100%;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Style the mobile navbar ul */
#navbar ul {
list-style: none;
padding: 0;
margin: 0;
}
#navbar ul li {
border-bottom: 1px solid #eee;
}
#navbar ul li a {
display: block;
padding: 0.75rem 1rem;
color: black;
text-decoration: none;
}
.quiz-timer {
font-size: 4rem;
}
.quiz-question {
font-size: 1.4rem; /* Smaller text */
}
.quiz-option {
font-size: 1.4rem; /* Smaller text */
}
.scroll-container {
position: relative;
overflow: hidden; /* Hide anything outside this container */
width: 100%; /* Adjust container width as needed */
left: 0;
background: #a3ff98; /* Match background to the gradient start */
height: 100%;
margin-right: 10%;
}
.discount-message {
position: absolute;
top: 50%;
/* Start the text fully off-screen to the right */
left: 100%;
transform: translateY(-50%);
text-overflow: ellipsis;
white-space: nowrap;
margin: 0;
font-size: 16px;
font-weight: bold;
animation: scroll-left 6s linear infinite;
}
/* Gradient overlay on the left edge */
.scroll-container::before {
z-index: 1000;
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 50px; /* Adjust width to control fade distance */
background: linear-gradient(to right, #a3ff98, rgba(255, 255, 255, 0));
pointer-events: none; /* Allow clicks to pass through */
}
/* Gradient overlay on the right edge */
.scroll-container::after {
z-index: 1000;
content: "";
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 50px; /* Adjust fade distance */
background: linear-gradient(to left, #a3ff98, rgba(255, 255, 255, 0));
pointer-events: none;
}
@keyframes scroll-left {
0% {
/* Start at the right edge of the container */
transform: translateX(0%) translateY(-50%);
}
100% {
/* End past the left edge of the container */
transform: translateX(-200%) translateY(-50%);
}
}
}
#mobile-logo {
position: fixed;
top: 0.5rem; /* Adjust spacing as needed */
left: 0.5rem; /* Adjust spacing as needed */
width: 30px; /* Make the icon tiny */
height: auto;
cursor: pointer;
z-index: 1000; /* Ensure it's above other content */
}
/* Hide the mobile logo by default */
#mobile-logo {
display: none;
}
/* Back button: tiny and fixed at top left */
#mobile-back {
position: fixed;
top: 0.5rem;
left: 0.5rem;
width: 30px; /* Adjust size as needed */
height: auto;
cursor: pointer;
z-index: 1000;
display: none; /* Hide by default on desktop */
}
/* Burger menu button: fixed at top right */
#burger-menu {
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 1000;
font-size: 1.5rem;
background: none;
border: none;
cursor: pointer;
display: none; /* Hide by default on desktop */
}
/* Mobile styles */
@media (max-width: 600px) {
#mobile-back,
#burger-menu {
display: block;
}
#navbar {
display: none;
position: absolute;
}
/* You can further style the nav menu for mobile here */
#navbar ul {
list-style: none;
padding: 0;
margin: 0;
}
#navbar ul li {
border-bottom: 1px solid #eee;
}
#navbar ul li a {
display: block;
padding: 0.75rem 1rem;
color: black;
text-decoration: none;
}
}
/* On mobile, hide the default logo and show the mobile logo */
@media (max-width: 600px) {
.logo {
display: none;
}
#mobile-logo {
display: block;
}
}
.has-submenu .submenu {
display: none;
}
/* When the submenu should be open (via JS toggle or hover) */
.has-submenu.active .submenu {
display: block;
}
/* Alternatively, if you want to open on hover (desktop only), you can add: */
@media (min-width: 601px) {
.has-submenu:hover .submenu {
display: block;
}
}
@media (max-width: 600px) {
#navbar ul li a {
display: block;
width: 100%;
padding: 0.75rem 1rem;
color: black;
text-decoration: none;
}
}
/* Group Plan User Input Styles */
.user-input {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 1rem 0; /* Provides spacing from surrounding elements */
}
.user-input label {
font-size: 0.9rem;
font-weight: bold;
color: inherit;
}
.user-input span {
font-size: 0.9rem;
min-width: 20px;
text-align: center;
}
.user-input input[type="range"] {
display: block; /* Hidden on desktop */
width: 100%;
}
main { background: none; } /* fix starfield creeping through */
</style>
<script src="https://js.stripe.com/v3/"></script>
<script>
/* ------------------ GLOBAL STATE & DEBOUNCE UTILS ------------------ */
const stripe = Stripe("pk_live_51OY9gGGxWcYgHR5Tap4m8YP4YD1z6hGYrnHs9JZEv7bH1sGt1tYLCuqBptcdp5F8orIWnAgMJM5WxaX34pA4yeRq00T4QhtSn6");
let discountSpecificCourseType = ""; // When set, only courses of this type get the extra discount.
let isProcessing = false;
let currentDiscountCode = "";
const originalPrices = {};
const discountThresholds = { mentoring: 2, consulting: 2, product: 10, course: 18 };
let discountUsed = { mentoring: false, consulting: false, product: false, course: false };
let selectedPlan = null;
let discountMentoringAvailable = false;
let discountConsultingAvailable = false;
let discountProductsAvailable = false;
let discountCoursesAvailable = false;
let discountAdditionalCoursesUnlocked = false;
let discountPrompted = { mentoring: false, consulting: false, product: false, course: false };
let selectionList = [];
let additionalProductsVisible = false;
const orbitalCapacities = [2, 8, 8, 18];
const orbitalRadii = [20, 40, 60, 80];
const exchangeRates = {
GBP: 1,
USD: 1.2821, // 1 GBP ≈ 1.2821 USD
EUR: 1.1667, // 1 GBP ≈ 1.1667 EUR
JPY: 188.4615, // 1 GBP ≈ 188.46 JPY
INR: 106.5385, // 1 GBP ≈ 106.54 INR
CAD: 1.7308, // 1 GBP ≈ 1.7308 CAD
AUD: 1.9487 // 1 GBP ≈ 1.9487 AUD
};
// Detect the user's locale as before, but fallback to GBP if the detected currency isn't in our rates
const browserLocale = navigator.language || 'en-GB';
const defaultCurrency = new Intl.NumberFormat(browserLocale, { style: 'currency', currency: 'GBP' })
.resolvedOptions().currency || 'GBP';
const userCurrency = defaultCurrency in exchangeRates ? defaultCurrency : 'GBP';
let selectedCurrency = userCurrency;
// Make yearly the default billing option
let currentBilling = "yearly";
let enterpriseUsers = 1;
let discountCodeActive = false;
// Quiz
let quizCompleted = false; // Track if the quiz has been completed
// Debounce utility to limit rapid function calls
function debounce(func, delay) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => { func.apply(this, args); }, delay);
};
}
/* ------------------ BASE DATA ------------------ */
const basePlans = [
{
name: "Student",
description: "Establish your online presence.",
features: ["Build your Website!", "Private Personal Email", "CV Hosting", "Integrated Note-Taking", "Secure Cloud Data & Code Hosting", "Protect your data from AI", "Donation Portal to Support You"],
bestDeal: true,
pricingGBP: {
monthly: 10,
four_months: 9.5,
yearly: 9
},
stripePriceIds: {
monthly: "price_1REoA3GxWcYgHR5Tsm8YrHBY", // Updated
four_months: "price_1REoA3GxWcYgHR5TVjOXDDIr", // Updated
yearly: "price_1REoA3GxWcYgHR5TzY1OxgKv" // Updated
},
},
{
name: "Professional",
description: "Distinguish yourself as the first search result.",
features: ["Custom Domain Website", "Unlimited Email Aliases", "All-in-One System for Notes", "Secure & Private", "Bespoke Web-Design", "Donation Portal", "Integrated Payments System for your Digital Books"],
pricingGBP: { monthly: 49, four_months: 45, yearly: 39 },
stripePriceIds: {
monthly: "price_1REoA0GxWcYgHR5TlDUsvAJN", // Updated
four_months: "price_1REoA0GxWcYgHR5TrwuUJgcY", // Updated
yearly: "price_1REoA0GxWcYgHR5TWtYqu18a" // Updated
}
},
{
name: "Group",
description: "Form your network, accelerate your research, and be at the cutting edge.",
features: ["Organisational Tools", "All-in-One Collaborative & Secure System", "Unlimited Group Email Accounts", "Bespoke Group Websites", "We Promote your Group via Social Platforms", "Donation Portal", "Integrated Payments Solutions for your Courses & Digital Books"],
pricingGBP: { monthly: 60, four_months: 56, yearly: 50 },
stripePriceIds: {
monthly: "price_1REoAGGxWcYgHR5TbQVfsVx4", // Updated
four_months: "price_1REoAGGxWcYgHR5Te4NrzpxX", // Updated
yearly: "price_1REoAHGxWcYgHR5ThzTomcpf" // Updated
},
perUserGBP: 9,
}
];
const billingOptions = { monthly: "Monthly", four_months: "Four mo", yearly: "Yearly" };
const baseProducts = [
{
name: "AI Assistant",
category: "product",
priceGBP: 10,
get electronCount() { return 1; },
stripePriceIds: {
monthly: 'price_1REoAdGxWcYgHR5TdoxcvhME', // Updated
four_months: 'price_1REoAdGxWcYgHR5T0DCUVXZO', // Updated
yearly: 'price_1REoAdGxWcYgHR5T47TqQjO2', // Updated
},
discountPriceIds: {
monthly: 'price_1REoAdGxWcYgHR5TV7rK7EBf', // Updated
four_months: 'price_1REoAdGxWcYgHR5T2vdvmlKO', // Updated
yearly: 'price_1REoAdGxWcYgHR5T1iFKajvt' // Updated
}
},
{
name: "Website Analytics",
category: "product",
priceGBP: 10,
get electronCount() { return 1; },
stripePriceIds: {
monthly: 'price_1REoAWGxWcYgHR5TjTmLJ81G', // Updated
four_months: 'price_1REoAWGxWcYgHR5TyROubMIz', // Updated
yearly: 'price_1REoAWGxWcYgHR5TklbC1nKP', // Updated
},
discountPriceIds: {
monthly: 'price_1REoAWGxWcYgHR5TNU4Zyg32', // Updated
four_months: 'price_1REoAVGxWcYgHR5TmanBxYTj', // Updated
yearly: 'price_1REoAVGxWcYgHR5TChyl5Eax' // Updated
}
}
];
const baseCourses = [
{
name: "Physics",
category: "course",
priceGBP: 249,
courseType: "Basic",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoAkGxWcYgHR5Titmg2WWn" // Updated
},
{
name: "Biology",
category: "course",
priceGBP: 249,
courseType: "Basic",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoAfGxWcYgHR5TweoeYxdg" // Updated
},
{
name: "Coding for Scientists",
category: "course",
priceGBP: 299,
courseType: "Advanced",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoA9GxWcYgHR5TKskzDwb7" // Updated
},
{
name: "Linux for Scientists",
category: "course",
priceGBP: 299,
courseType: "University",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoA7GxWcYgHR5TTRxyTiLM" // Updated
},
{
name: "Design",
category: "course",
priceGBP: 299,
courseType: "Advanced",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoACGxWcYgHR5TKHrgNu0S" // Updated
},
{
name: "Mathematics Course",
category: "course",
priceGBP: 249,
courseType: "Basic",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoAFGxWcYgHR5TBB2HuX1j" // Updated
},
{
name: "Chemistry Course",
category: "course",
priceGBP: 249,
courseType: "Basic",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoAhGxWcYgHR5TJqQ7VYcv" // Updated
},
{
name: "Entrepreneurship Dragon",
category: "course",
priceGBP: 299,
courseType: "Entrepreneurship",
get electronCount() { return this.priceGBP < 50 ? 3 : 4; },
stripePriceId: "price_1REoA5GxWcYgHR5Ta1xSFjS6" // Updated
}
];
const planAddons = {
Student: {
name: "Mentoring",
category: "mentoring",
priceGBP: 20,
get electronCount() { return 4; },
stripePriceIds: {
monthly: 'price_1REo9xGxWcYgHR5TxjgMPfEC', // Updated
four_months: 'price_1REo9xGxWcYgHR5TlItilby4', // Updated
yearly: 'price_1REo9xGxWcYgHR5ThLDS1WGJ', // Updated
}
},
Professional: {
name: "Consulting",
category: "consulting",
priceGBP: 20,
get electronCount() { return 5; },
stripePriceIds: {
monthly: 'price_1REo9uGxWcYgHR5TRa16ycNn', // Updated
four_months: 'price_1REo9uGxWcYgHR5TepnX31Gh', // Updated
yearly: 'price_1REo9uGxWcYgHR5TE2lfAHdS', // Updated
}
},
Group: {
name: "Consulting",
category: "consulting",
priceGBP: 20,
get electronCount() { return 5; },
stripePriceIds: {
monthly: 'price_1REo9uGxWcYgHR5TRa16ycNn', // Updated
four_months: 'price_1REo9uGxWcYgHR5TepnX31Gh', // Updated
yearly: 'price_1REo9uGxWcYgHR5TE2lfAHdS', // Updated
}
}
};
function getMentoringPlusStudent() {
const basicPlan = basePlans.find(p => p.name === "Student");
const mentoringAddon = planAddons["Student"];
return {
name: "Mentoring + Student",
category: "mentoring",
priceGBP: basicPlan.pricingGBP[currentBilling] + mentoringAddon.priceGBP,
electronCount: mentoringAddon.electronCount
};
}
/* ------------------ PAYMENT TABLE FUNCTIONS ------------------ */
// DRY pricing helper for all plans
function getPlanPricing(plan, billingCycle) {
let basePrice;
if (plan.name === "Group") {
// For Group plans add per-user cost based on enterpriseUsers
basePrice = plan.pricingGBP[billingCycle] + enterpriseUsers * (plan.perUserGBP || 0);
} else {
basePrice = plan.pricingGBP[billingCycle];
}
// Apply discount if active
if (discountCodeActive) basePrice *= 0.5;
// Convert price into selected currency
const convertedPrice = convertToCurrency(basePrice);
let monthlyRate = convertedPrice;
let fullBillingText = "";
if (billingCycle === "four_months") {
fullBillingText = `${formatPrice(convertedPrice * 4)} billed every 4 months`;
} else if (billingCycle === "yearly") {
fullBillingText = `${formatPrice(convertedPrice * 12)} billed annually`;
}
// For monthly, the monthlyRate is already the converted per-month price.
return { monthlyRate, fullBillingText };
}
function convertToCurrency(usd) { return usd * exchangeRates[selectedCurrency]; }
function formatPrice(amount) {
const rounded = Math.round(amount * 2) / 2;
// Determine if the rounded value has a fractional part.
const fractionDigits = (rounded % 1 === 0) ? 0 : 1;
return new Intl.NumberFormat(browserLocale, {
style: "currency",
currency: selectedCurrency,
minimumFractionDigits: fractionDigits,
maximumFractionDigits: fractionDigits
}).format(rounded);
}
function updateGroupUsers(val) {
enterpriseUsers = parseInt(val) || 1;
updateGroupPriceOnly();
}
function updateGroupPriceOnly() {
const enterprisePriceElem = document.querySelector('[data-enterprise-price]');
if (!enterprisePriceElem) return;
const { monthlyRate, fullBillingText } = getPlanPricing(basePlans[2], currentBilling);
const monthlyText = `${formatPrice(monthlyRate)}/mo`;
enterprisePriceElem.innerHTML = currentBilling === "monthly"
? monthlyText
: `${monthlyText} <small>${fullBillingText}</small>`;
const input = document.querySelector('[data-enterprise-input]');
if (input) input.value = enterpriseUsers;
}
/* ------------------ INITIALIZE PAYMENT TABLE ------------------ */
function createPaymentTable() {
document.body.style.overflow = "";
const container = document.getElementById("payment-table");
container.innerHTML = `
<div class="billing-controls">
<!-- Currency and Discount Controls on top -->
<div class="billing-extra">
<div class="currency-select">
<label for="currency">Currency:</label>
<select id="currency" onchange="updateCurrency(this.value)">
${Object.keys(exchangeRates).map(cur => `
<option value="${cur}" ${cur === selectedCurrency ? "selected" : ""}>${cur}</option>
`).join("")}
</select>
</div>
<div class="discount-input">
<label for="discount-code">Discount:</label>
<input
type="text"
id="discount-code"
name="discount-code"
autocomplete="off"
placeholder="Enter code"
value="${currentDiscountCode}"
onkeyup="debouncedApplyDiscountCode(event)"
/>
</div>
</div>
<!-- Billing Courses Button -->
<div class="billing-courses">
<button class="plan-button" onclick="jumpToCourses()">
<i class="icon-courses"></i> Jump to Courses
</button>
</div>
<!-- Billing Toggle now only contains the websites buttons -->
<div class="billing-toggle">
<div class="billing-websites">
<span class="billing-icon"><i class="icon-website"></i></span>
${Object.entries(billingOptions).slice(0, 3).map(([key, label]) => `
<button onclick="updateBilling('${key}')" class="${key === currentBilling ? 'active' : ''}">
${label}
</button>
`).join("")}
</div>
</div>
</div>
<div id="plan-cards">
${basePlans.map(plan => {
const { monthlyRate, fullBillingText } = getPlanPricing(plan, currentBilling);
const monthlyText = `${formatPrice(monthlyRate)}/mo`;
return `
<div class="plan-wrapper">
<div class="plan">
${plan.bestDeal ? `<div class="best-deal">Best Deal</div>` : ""}
<div class="plan-title">${plan.name}</div>
<div class="plan-price" ${plan.name === "Group" ? 'data-enterprise-price' : ''}>
${currentBilling === "monthly"
? monthlyText
: `${monthlyText} <small>${fullBillingText}</small>`}
</div>
<div class="plan-desc">${plan.description}</div>
<ul class="plan-features">
${plan.features.map(f => `<li>${f}</li>`).join("")}
</ul>
${
plan.name === "Group" ? `
<div class="user-input">
<label for="enterprise-users">Users:</label>
<span id="user-count-display">${enterpriseUsers}</span>
<input type="range" id="enterprise-users-slider" min="1" max="100" value="${enterpriseUsers}"
oninput="updateGroupUsers(this.value); document.getElementById('user-count-display').textContent = this.value;"
data-enterprise-slider />
</div>
` : ""}
<button class="plan-button" onclick="choosePlan('${plan.name}')">Choose Plan</button>
</div>
</div>
`;
}).join("")}
</div>
`;
setTimeout(updateGroupPriceOnly, 50);
}
function updateBilling(type) {
if (type === currentBilling) return;
const billingToggle = document.querySelector(".billing-toggle");
if (billingToggle) {
billingToggle.classList.add('slide-out');
setTimeout(() => {
currentBilling = type;
createPaymentTable();
const newBillingToggle = document.querySelector(".billing-toggle");
if (newBillingToggle) {
newBillingToggle.classList.add('slide-in');
setTimeout(() => { newBillingToggle.classList.remove('slide-in'); }, 300);
}
}, 300);
}
}
function updateCurrency(newCurrency) {
selectedCurrency = newCurrency;
createPaymentTable();
}
const debouncedApplyDiscountCode = debounce(function(e) {
applyDiscountCode(e);
}, 500);
function applyDiscountCode(e) {
const code = e.target.value.trim().toUpperCase();
currentDiscountCode = code; // Save the entered code globally
discountCodeActive = (code === "QUANTUM50");
// Only re-render when the user presses Enter
if (e.key === "Enter") {
createPaymentTable();
e.preventDefault();
}
}
function choosePlan(planName) {
selectedPlan = planName;
selectionList = [];
discountMentoringAvailable = false;
discountConsultingAvailable = false;
discountProductsAvailable = false;
discountCoursesAvailable = false;
discountPrompted = { mentoring: false, consulting: false, product: false, course: false };
discountUsed = { mentoring: false, consulting: false, product: false, course: false };
additionalProductsVisible = true;
showSelection(true);
}
function jumpToCourses() {
selectedPlan = "Student";
selectionList = [];
discountMentoringAvailable = false;
discountConsultingAvailable = false;
discountProductsAvailable = false;
discountCoursesAvailable = false;
discountPrompted = { mentoring: false, consulting: false, product: false, course: false };
discountUsed = { mentoring: false, consulting: false, product: false, course: false };
planAddons["Student"] = getMentoringPlusStudent();
showSelection(false);
}
/* ------------------ LOGO FUNCTIONS ------------------ */
function toggleBurgerMenu() {
const mobileNav = document.getElementById("navbar");
if (!mobileNav) return;
if (mobileNav.style.display === "block") {
mobileNav.style.display = "none";
} else {
mobileNav.style.display = "block";
}
}
function moveLogoToHeader() {
// Select the logo inside the anchor (using a descendant selector)
const logoWrapper = document.querySelector('a.logo > #logo-wrapper');
const headerLeft = document.querySelector('.header-left');
if (logoWrapper && headerLeft) {
// Remove the logo from its current parent so it doesn't get duplicated
logoWrapper.parentNode.removeChild(logoWrapper);
// Clear any existing content in header-left to avoid duplicates
headerLeft.innerHTML = '';
// Append the detached logo element to the header
headerLeft.appendChild(logoWrapper);
logoWrapper.style.cursor = "pointer";
logoWrapper.onclick = handleLogoBack;
}
}
function moveLogoBack() {
// Now select the logo from within the header-left
const logoWrapper = document.querySelector('.header-left > #logo-wrapper');
const originalContainer = document.querySelector('a.logo');
if (logoWrapper && originalContainer) {
// Remove the logo from the header to avoid duplication
logoWrapper.parentNode.removeChild(logoWrapper);
// Clear the anchor's content (if necessary) and reinsert the logo
originalContainer.innerHTML = '';
originalContainer.appendChild(logoWrapper);
logoWrapper.style.cursor = "default";
logoWrapper.onclick = null;
}
}
function handleLogoBack() {
backToPayment();
}
function setMainPadding(paddingValue) {
const mainEl = document.querySelector("main");
if (mainEl) {
mainEl.style.padding = paddingValue;
}
}
/* ------------------ SELECTION & ATOM VIEW ------------------ */
function showSelection(includeAdditional) {
setMainPadding("0"); // Set padding to 0 when opening the second table
// Hide the footer when entering the selection view
const footer = document.querySelector('footer');
if (footer) {
footer.style.display = 'none';
}
document.body.style.overflow = "hidden";
const container = document.getElementById("payment-table");
container.classList.add("fade-out");
setTimeout(() => {
container.innerHTML = buildSelectionHTML(includeAdditional);
container.classList.remove("fade-out");
container.classList.add("fade-in");
setTimeout(() => { container.classList.remove("fade-in"); }, 500);
moveLogoToHeader();
updateAtomSVG();
updateDiscountMessage();
scrollToDiscountedItem();
attachAtomTripleClick();
attachAtomSingleClick();
}, 1000);
}
function backToPayment() {
setMainPadding(""); // Restore original padding when returning
// Display the footer again when leaving the selection view
const footer = document.querySelector('footer');
if (footer) {
footer.style.display = 'block';
}
document.body.style.overflow = "";
const container = document.getElementById("payment-table");
container.classList.add("fade-out");
moveLogoBack();
setTimeout(() => {
createPaymentTable();
container.classList.remove("fade-out");
container.classList.add("fade-in");
setTimeout(() => { container.classList.remove("fade-in"); }, 500);
}, 500);
}
const additionalProductsByPlan = {
"Student": [
{
name: "Operating System",
category: "product",
priceGBP: 10,
electronCount: 2,
stripePriceIds: {
monthly: 'price_1REo9nGxWcYgHR5TXi8qas6X', // Updated
four_months: 'price_1REo9nGxWcYgHR5TKQOCdIQv', // Updated
yearly: 'price_1REo9nGxWcYgHR5T0fffC74r', // Updated
},
discountPriceIds: {
monthly: 'price_1REo9nGxWcYgHR5T49FF3aB8', // Updated
four_months: 'price_1REo9nGxWcYgHR5TblsUJ63e', // Updated
yearly: 'price_1REo9nGxWcYgHR5T4bAiYIDy', // Updated
}
},
{
name: "AI Assistant",
category: "product",
priceGBP: 10,
electronCount: 1,
stripePriceIds: {
monthly: 'price_1QifhQGxWcYgHR5Thw2GRDdy', // Updated
four_months: 'price_1QifhQGxWcYgHR5TiJTfVYpX', // Updated
yearly: 'price_1QifhQGxWcYgHR5T0B5ai9kV', // Updated
},
discountPriceIds: {
monthly: 'price_1REoAdGxWcYgHR5TV7rK7EBf', // Updated
four_months: 'price_1REoAdGxWcYgHR5T2vdvmlKO', // Updated
yearly: 'price_1REoAdGxWcYgHR5T1iFKajvt', // Updated
}
}
],
"Professional": [
{
name: "Website Analytics",
category: "product",
priceGBP: 10,
electronCount: 1,
stripePriceIds: {
monthly: 'price_1QifmFGxWcYgHR5TOB1zkEis', // Updated
four_months: 'price_1QifmFGxWcYgHR5TvFhR8g9I', // Updated
yearly: 'price_1QifmFGxWcYgHR5TR5kBcN5J', // Updated
},
discountPriceIds: {
monthly: 'price_1REoAWGxWcYgHR5TNU4Zyg32', // Updated
four_months: 'price_1REoAVGxWcYgHR5TmanBxYTj', // Updated
yearly: 'price_1REoAVGxWcYgHR5TChyl5Eax', // Updated
}
},
{
name: "Operating System",
category: "product",
priceGBP: 10,
electronCount: 2,
stripePriceIds: {
monthly: 'price_1REo9nGxWcYgHR5TXi8qas6X', // Updated
four_months: 'price_1REo9nGxWcYgHR5TKQOCdIQv', // Updated
yearly: 'price_1REo9nGxWcYgHR5T0fffC74r', // Updated
},
discountPriceIds: {
monthly: 'price_1REo9nGxWcYgHR5T49FF3aB8', // Updated
four_months: 'price_1REo9nGxWcYgHR5TblsUJ63e', // Updated
yearly: 'price_1REo9nGxWcYgHR5T4bAiYIDy', // Updated
}
},
{
name: "AI Assistant",
category: "product",
priceGBP: 20,
electronCount: 2,
stripePriceIds: {
monthly: 'price_1REiVRGxWcYgHR5TgpRb4ic9', // Updated
four_months: 'price_1REiyGGxWcYgHR5T9dk1afLL', // Updated
yearly: 'price_1REiyGGxWcYgHR5TSQf8kCVR', // Updated
},
discountPriceIds: {
monthly: 'price_1REoAdGxWcYgHR5TV7rK7EBf', // Updated
four_months: 'price_1REoAdGxWcYgHR5T2vdvmlKO', // Updated
yearly: 'price_1REoAdGxWcYgHR5T1iFKajvt', // Updated
}
}
],
"Group": [
{
name: "Website Analytics",
category: "product",
priceGBP: 10,
electronCount: 1,
stripePriceIds: {
monthly: 'price_1REoAWGxWcYgHR5TjTmLJ81G', // Updated
four_months: 'price_1REoAWGxWcYgHR5TyROubMIz', // Updated
yearly: 'price_1REoAWGxWcYgHR5TklbC1nKP', // Updated
},
discountPriceIds: {
monthly: 'price_1REoAWGxWcYgHR5TNU4Zyg32', // Updated
four_months: 'price_1REoAVGxWcYgHR5TmanBxYTj', // Updated
yearly: 'price_1REoAVGxWcYgHR5TChyl5Eax', // Updated
}
},
{
name: "Operating System",
category: "product",
priceGBP: 10,
electronCount: 2,
stripePriceIds: {
monthly: 'price_1REo9nGxWcYgHR5TXi8qas6X', // Updated
four_months: 'price_1REo9nGxWcYgHR5TKQOCdIQv', // Updated
yearly: 'price_1REo9nGxWcYgHR5T0fffC74r', // Updated
},
discountPriceIds: {
monthly: 'price_1REo9nGxWcYgHR5T49FF3aB8', // Updated
four_months: 'price_1REo9nGxWcYgHR5TblsUJ63e', // Updated
yearly: 'price_1REo9nGxWcYgHR5T4bAiYIDy', // Updated
}
},
{
name: "Advanced AI",
category: "product",
priceGBP: 20,
electronCount: 2,
stripePriceIds: {
monthly: 'price_1REo9rGxWcYgHR5T5XT1ntbF', // Updated
four_months: 'price_1REo9rGxWcYgHR5TLbCRMNL9', // Updated
yearly: 'price_1REo9rGxWcYgHR5THL9e4Hwd', // Updated
},
discountPriceIds: {
monthly: 'price_1REo9rGxWcYgHR5TpsXLdwcj', // Updated
four_months: 'price_1REo9rGxWcYgHR5TYWqZ2qiT', // Updated
yearly: 'price_1REo9rGxWcYgHR5TKJ0hveJ5', // Updated
}
}
]
};
function buildSelectionHTML(includeAdditional) {
const courseCategoriesSet = new Set(baseCourses.map(c => c.courseType));
const courseCategories = Array.from(courseCategoriesSet);
let additionalProductsHTML = "";
if (includeAdditional) {
const addProds = additionalProductsByPlan[selectedPlan] || [];
if (addProds.length) {
additionalProductsHTML = `
<div class="selection-section" id="product">
<h2>Additional Products</h2>
<div class="selection-cards swipe-in">
${addProds.map(p => buildSelectionCard(p, true)).join("")}
</div>
</div>
`;
}
}
let coursesHTML = "";
courseCategories.forEach(cat => {
const courses = baseCourses.filter(c => c.courseType === cat);
if (courses.length) {
coursesHTML += `
<div class="selection-section" id="${cat.toLowerCase()}-courses">
<h2>${cat} Courses</h2>
<div class="selection-cards">
${courses.map(c => buildSelectionCard(c)).join("")}
</div>
</div>
`;
}
});
const planAddonData = planAddons[selectedPlan] || null;
// Build submenu for courses with an indicator; clicking toggles submenu visibility
const coursesSubmenuHTML = courseCategories.map(cat => `
<li><a href="#${cat.toLowerCase()}-courses" style="color: black;">${cat}</a></li>
`).join("");
const headerHTML = `
<div class="header-divider" id="header-divider">
<div class="header-left" id="logo-wrapper">
<img src="/favicon/icon.svg" alt="Back" id="mobile-logo" onclick="handleLogoBack()" style="cursor: pointer;">
</div>
<div class="header-center">
<svg id="atom-svg" viewBox="-85 -85 170 170" preserveAspectRatio="xMidYMid meet" style="overflow: visible;">
<circle cx="0" cy="0" r="10" fill="#a3ff98">
<animate attributeName="fill" values="#ebff00;#00ffff;#ebff00" dur="10s" repeatCount="indefinite" />
</circle>
<circle cx="0" cy="0" r="20" stroke="#aaa" fill="none"/>
<circle cx="0" cy="0" r="40" stroke="#aaa" fill="none"/>
<circle cx="0" cy="0" r="60" stroke="#aaa" fill="none"/>
<circle cx="0" cy="0" r="80" stroke="#aaa" fill="none"/>
</svg>
</div>
<div class="header-right">
<button id="burger-menu" onclick="toggleBurgerMenu()">☰</button>
<nav id="navbar">
<ul>
<li>
<a href="#products">🛒 Products</a>
</li>
<li class="has-submenu" onclick="toggleCoursesSubmenu()">
<a href="javascript:void(0);" style="color: black; display: block;">
🎓 Courses <span id="courses-indicator">▾</span>
</a>
<ul class="submenu" id="courses-submenu" style="display: none;">
${coursesSubmenuHTML}
</ul>
</li>
<li>
<a href="#plan-addon">
${selectedPlan === "Student" ? "🤝 Mentoring" : "💼 Consulting"}
</a>
</li>
</ul>
</nav>
</div>
</div>
`;
return `
<div class="selection-container">
${headerHTML}
<div class="body-divider">
${additionalProductsHTML}
${coursesHTML}
${
planAddonData
? `<div class="selection-section" id="plan-addon">
<h2>Plan-Specific Add-on</h2>
<div class="selection-cards">
${buildSelectionCard(planAddonData, true)}
</div>
</div>`
: ""
}
</div>
</div>
<div class="footer-divider">
<div class="scroll-container">
<span id="discount-message" class="discount-message"></span>
</div>
<button class="checkout-btn" onclick="finalizeCheckout()">Checkout</button>
</div>
`;
}
function buildSelectionCard(item, isAddOn=false) {
const isSelected = selectionList.some(sel => sel.name === item.name);
const selectedItem = selectionList.find(sel => sel.name === item.name);
let priceText = formatPrice(convertToCurrency(item.priceGBP));
if (isAddOn || item.category === "product") { priceText += "/mo"; }
return `
<div class="selection-card ${isSelected ? 'selected' : ''}" data-name="${item.name}" onclick="toggleItem(event, '${item.name}')">
<img src="https://via.placeholder.com/150" alt="${item.name}">
<h3>${item.name}</h3>
<p>${item.category === "course" ? "Expand your skills." : "Boost your plan."}</p>
<div class="price">${priceText}</div>
<div class="electron-count">⚛ ${item.electronCount}</div>
${isSelected && selectedItem && selectedItem.discountApplied ? `<div class="discount-sticker">Discount Applied!</div>` : ""}
</div>
`;
}
function toggleItem(event, itemName) {
event.stopPropagation();
const svg = document.getElementById("atom-svg");
if (svg && svg.querySelector("foreignObject") && itemName && isCourse(itemName)) {
updateAtomSVG();
attachAtomTripleClick();
}
if (isProcessing) return;
isProcessing = true;
const allItems = [...baseCourses, ...baseProducts];
if (planAddons[selectedPlan]) allItems.push(planAddons[selectedPlan]);
if (additionalProductsVisible && additionalProductsByPlan[selectedPlan]) {
allItems.push(...additionalProductsByPlan[selectedPlan]);
}
const itemData = allItems.find(x => x.name === itemName);
if (!itemData) { isProcessing = false; return; }
let discountAvailable = false;
if (itemData.category === "course") {
// 1) quiz‑unlocked Entrepreneurship
if (discountAdditionalCoursesUnlocked && itemData.courseType === "Entrepreneurship") {
discountAvailable = true;
}
// 2) any other “specific” course code you might have set
else if (discountSpecificCourseType && itemData.courseType === discountSpecificCourseType) {
discountAvailable = true;
}
// 3) the generic courses‑over‑18‑electron bonus
else {
discountAvailable = discountCoursesAvailable;
}
} else {
// existing branches for mentoring, consulting, products
if (itemData.category === "mentoring") discountAvailable = discountMentoringAvailable;
if (itemData.category === "consulting") discountAvailable = discountConsultingAvailable;
if (itemData.category === "product") discountAvailable = discountProductsAvailable;
}
const currentlySelected = selectionList.find(x => x.name === itemName);
if (currentlySelected) {
if (discountAvailable && !currentlySelected.discountApplied) {
if (!originalPrices[itemName]) originalPrices[itemName] = currentlySelected.priceGBP;
currentlySelected.priceGBP *= 0.5;
currentlySelected.discountApplied = true;
if (itemData.category === "mentoring") { discountMentoringAvailable = false; discountUsed.mentoring = true; }
else if (itemData.category === "consulting") { discountConsultingAvailable = false; discountUsed.consulting = true; }
else if (itemData.category === "product") { discountProductsAvailable = false; discountUsed.product = true; }
else if (itemData.category === "course") { discountCoursesAvailable = false; discountUsed.course = true; }
updateSelectionCard(itemName);
checkDiscounts().then(() => { isProcessing = false; });
return;
}
removeItem(currentlySelected).then(() => {
if (itemName === "Mentoring + Student") {
additionalProductsVisible = false;
showSelection(additionalProductsVisible);
}
checkDiscounts().then(() => { isProcessing = false; });
});
} else {
const newItem = {
name: itemData.name,
category: itemData.category,
priceGBP: itemData.priceGBP,
electronCount: itemData.electronCount,
stripePriceIds: itemData.stripePriceIds || null, // ← add this
stripePriceId: itemData.stripePriceId || null, // keep for courses
discountApplied: false,
courseType: itemData.courseType || ""
};
originalPrices[newItem.name] = newItem.priceGBP;
selectionList.push(newItem);
if (discountAvailable) {
newItem.priceGBP *= 0.5;
newItem.discountApplied = true;
if (itemData.category === "mentoring") { discountMentoringAvailable = false; discountUsed.mentoring = true; }
else if (itemData.category === "consulting") { discountConsultingAvailable = false; discountUsed.consulting = true; }
else if (itemData.category === "product") { discountProductsAvailable = false; discountUsed.product = true; }
else if (itemData.category === "course") { discountCoursesAvailable = false; discountUsed.course = true; }
}
animateAddItem(newItem).then(() => {
updateSelectionCard(itemName);
if (itemName === "Mentoring + Student") {
additionalProductsVisible = true;
const bodyDiv = document.querySelector('.body-divider');
if (bodyDiv) { bodyDiv.scrollTop = bodyDiv.scrollHeight - 100; }
showSelection(additionalProductsVisible);
}
checkDiscounts().then(() => { isProcessing = false; });
});
}
}
function isCourse(itemName) {
const allCourses = baseCourses.map(c => c.name);
return allCourses.includes(itemName);
}
function updateSelectionCard(itemName) {
const cardElement = document.querySelector(`.selection-card[data-name="${itemName}"]`);
if (!cardElement) return;
const selectedItem = selectionList.find(x => x.name === itemName);
const existingSticker = cardElement.querySelector(".discount-sticker");
if (selectedItem && selectedItem.discountApplied) {
if (!existingSticker) {
const newSticker = document.createElement("div");
newSticker.className = "discount-sticker";
newSticker.textContent = "Discount Applied!";
cardElement.appendChild(newSticker);
}
} else {
if (existingSticker) { existingSticker.remove(); }
}
}
function removeItem(item) {
return new Promise(resolve => {
const idx = selectionList.findIndex(i => i.name === item.name);
if (idx < 0) { resolve(); return; }
const cardElement = document.querySelector(`.selection-card[data-name="${item.name}"]`);
if (cardElement) {
cardElement.classList.remove("selected");
const sticker = cardElement.querySelector(".discount-sticker");
if (sticker) { sticker.remove(); }
}
if (item.discountApplied) {
if (item.category === "mentoring") discountUsed.mentoring = false;
if (item.category === "consulting") discountUsed.consulting = false;
if (item.category === "product") discountUsed.product = false;
if (item.category === "course") discountUsed.course = false;
}
const baseIndex = selectionList.slice(0, idx).reduce((sum, it) => sum + it.electronCount, 0);
const count = item.electronCount;
const tasks = [];
for (let i = 0; i < count; i++) {
const reversedIndex = count - 1 - i;
const globalIndex = baseIndex + reversedIndex;
tasks.push(() => animateElectronOut(cardElement, globalIndex, i * 150));
}
Promise.all(tasks.map(fn => fn())).then(() => {
selectionList.splice(idx, 1);
updateAtomSVG();
resolve();
});
});
}
function animateAddItem(item) {
return new Promise(resolve => {
const cardElement = document.querySelector(`.selection-card[data-name="${item.name}"]`);
if (!cardElement) { resolve(); return; }
cardElement.classList.add("selected");
if (item.discountApplied) {
const sticker = document.createElement("div");
sticker.className = "discount-sticker";
sticker.textContent = "Discount Applied!";
cardElement.appendChild(sticker);
}
const idx = selectionList.findIndex(x => x.name === item.name);
const baseIndex = selectionList.slice(0, idx).reduce((sum, it) => sum + it.electronCount, 0);
const tasks = [];
for (let i = 0; i < item.electronCount; i++) {
tasks.push(() => animateElectronIn(cardElement, baseIndex + i, i * 150));
}
Promise.all(tasks.map(fn => fn())).then(() => {
updateAtomSVG();
resolve();
});
});
}
function distributeElectrons(total) {
let remaining = total;
const out = [];
for (let i = 0; i < orbitalCapacities.length; i++) {
const used = Math.min(remaining, orbitalCapacities[i]);
out.push(used);
remaining -= used;
}
return out;
}
function getElectronCoordinates(shellIndex, indexInShell, shellCount) {
const r = orbitalRadii[shellIndex];
if (shellCount === 0) return { x: 0, y: 0 };
const angleStep = 360 / shellCount;
const angle = angleStep * indexInShell;
const rad = angle * Math.PI / 180;
return { x: r * Math.cos(rad), y: r * Math.sin(rad) };
}
function globalIndexToShell(globalIndex, distribution) {
let accum = 0;
for (let s = 0; s < distribution.length; s++) {
const c = distribution[s];
if (globalIndex < accum + c) { return { shellIndex: s, indexInShell: globalIndex - accum }; }
accum += c;
}
return { shellIndex: distribution.length - 1, indexInShell: 0 };
}
function updateAtomSVG() {
const svg = document.getElementById("atom-svg");
if (!svg) return;
svg.querySelectorAll("foreignObject").forEach(el => el.remove());
const header = document.getElementById("header-divider");
if (header) {
header.style.height = "25vh";
const bodyDivider = document.querySelector(".body-divider");
if (bodyDivider) { bodyDivider.style.top = "25vh"; }
}
svg.innerHTML = `
<circle cx="0" cy="0" r="10" fill="#a3ff98">
<animate attributeName="fill" values="#ebff00;#00ffff;#ebff00" dur="10s" repeatCount="indefinite" />
</circle>
<circle cx="0" cy="0" r="20" stroke="#aaa" fill="none"/>
<circle cx="0" cy="0" r="40" stroke="#aaa" fill="none"/>
<circle cx="0" cy="0" r="60" stroke="#aaa" fill="none"/>
<circle cx="0" cy="0" r="80" stroke="#aaa" fill="none"/>
`;
const total = selectionList.reduce((sum, it) => sum + it.electronCount, 0);
const dist = distributeElectrons(total);
dist.forEach((count, s) => {
const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
g.id = "orbital-" + (s + 1);
svg.appendChild(g);
for (let i = 0; i < count; i++) {
const { x, y } = getElectronCoordinates(s, i, count);
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", x);
circle.setAttribute("cy", y);
circle.setAttribute("r", s === 0 ? 3 : 4);
circle.setAttribute("fill", "black");
g.appendChild(circle);
}
});
svg.classList.add("orbit-rotation");
const nucleus = svg.querySelector("circle");
nucleus.classList.remove("nucleus-filled");
if (dist[0] === orbitalCapacities[0]) { nucleus.classList.add("nucleus-filled"); }
applyGlowIfDiscount();
}
function animateElectronIn(sourceEl, globalIndex, delayMs = 0) {
return new Promise(resolve => {
setTimeout(() => {
const total = selectionList.reduce((s, it) => s + it.electronCount, 0);
const dist = distributeElectrons(total);
const { shellIndex, indexInShell } = globalIndexToShell(globalIndex, dist);
const { x, y } = getElectronCoordinates(shellIndex, indexInShell, dist[shellIndex]);
const svg = document.getElementById("atom-svg");
const pt = svg.createSVGPoint();
pt.x = x; pt.y = y;
const screenPt = pt.matrixTransform(svg.getScreenCTM());
const [tx, ty] = [screenPt.x, screenPt.y];
const rect = sourceEl.getBoundingClientRect();
const sx = rect.left + rect.width / 2;
const sy = rect.top + rect.height / 2;
const electron = document.createElement("div");
electron.className = "flying-electron";
electron.style.left = sx + "px";
electron.style.top = sy + "px";
document.body.appendChild(electron);
electron.getBoundingClientRect();
electron.style.left = tx + "px";
electron.style.top = ty + "px";
electron.addEventListener("transitionend", () => { electron.remove(); resolve(); });
}, delayMs);
});
}
function animateElectronOut(targetEl, globalIndex, delayMs = 0) {
return new Promise(resolve => {
setTimeout(() => {
const total = selectionList.reduce((s, it) => s + it.electronCount, 0) + 1;
const dist = distributeElectrons(total);
const { shellIndex, indexInShell } = globalIndexToShell(globalIndex, dist);
const { x, y } = getElectronCoordinates(shellIndex, indexInShell, dist[shellIndex]);
const svg = document.getElementById("atom-svg");
const pt = svg.createSVGPoint();
pt.x = x; pt.y = y;
const sp = pt.matrixTransform(svg.getScreenCTM());
const rect = targetEl.getBoundingClientRect();
const tx = rect.left + rect.width / 2;
const ty = rect.top + rect.height / 2;
const angleDeg = Math.atan2(ty - sp.y, tx - sp.x) * (180 / Math.PI);
const photon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
photon.classList.add("flying-photon");
photon.setAttribute("viewBox", "0 0 10 10");
photon.innerHTML = `<path d="M0,5 Q2.5,0 5,5 T10,5" stroke="black" fill="none" stroke-width="1"/>`;
photon.style.position = "fixed";
photon.style.left = sp.x + "px";
photon.style.top = sp.y + "px";
photon.style.transform = `rotate(${angleDeg}deg)`;
document.body.appendChild(photon);
photon.getBoundingClientRect();
photon.style.left = tx + "px";
photon.style.top = ty + "px";
photon.style.opacity = "0";
photon.addEventListener("transitionend", () => { photon.remove(); resolve(); });
}, delayMs);
});
}
function applyGlowIfDiscount() {
let discountGlowing = false;
// 1) pull in every item you ever render as a <.selection-card>
const allItems = [
...baseCourses,
...baseProducts,
// only if we’re showing them
...(additionalProductsVisible && additionalProductsByPlan[selectedPlan]
? additionalProductsByPlan[selectedPlan]
: []),
...(planAddons[selectedPlan] ? [planAddons[selectedPlan]] : [])
];
// 2) now loop cards and match by name
document.querySelectorAll(".selection-card").forEach(card => {
const name = card.getAttribute("data-name");
const found = allItems.find(i => i.name === name);
if (!found) {
// this card isn’t one of ours
card.classList.remove("card-glow");
return;
}
// 3) same logic as before for “should this glow?”
let glow = false;
if (found.category === "mentoring" && discountMentoringAvailable) glow = true;
if (found.category === "consulting" && discountConsultingAvailable) glow = true;
if (found.category === "product" && discountProductsAvailable) glow = true;
if (found.category === "course" && discountCoursesAvailable) glow = true;
if (found.category === "course"
&& found.courseType === "Entrepreneurship"
&& discountAdditionalCoursesUnlocked) glow = true;
const selected = selectionList.some(x => x.name === name && x.discountApplied);
if (selected) {
card.classList.remove("card-glow");
} else if (glow) {
card.classList.add("card-glow");
discountGlowing = true;
} else {
card.classList.remove("card-glow");
}
});
// 4) nucleus flash if *any* card was glowing
if (discountGlowing) {
const svg = document.getElementById("atom-svg");
if (svg) {
const nucleus = svg.querySelector("circle");
nucleus.classList.add("nucleus-flash");
setTimeout(() => nucleus.classList.remove("nucleus-flash"), 1000);
}
}
}
function updateDiscountMessage() {
const msgElem = document.getElementById("discount-message");
if (!msgElem) return;
// Clear the message if the discount on courses has been applied (as before)
if (discountSpecificCourseType && discountUsed.course) {
msgElem.textContent = "";
return;
}
let discountCategories = [];
if (discountMentoringAvailable) discountCategories.push("mentoring");
if (discountConsultingAvailable) discountCategories.push("consulting");
if (discountProductsAvailable) discountCategories.push("product");
// Show a specific label if a specific course discount is active.
if (discountSpecificCourseType) {
discountCategories.push(discountSpecificCourseType + " courses");
} else if (discountCoursesAvailable) {
discountCategories.push("courses");
}
if (discountCategories.length > 0) {
if (discountCategories.length === 1) {
msgElem.textContent = "Discount unlocked for " + discountCategories[0] + "!";
} else if (discountCategories.length === 2) {
msgElem.textContent = "Discount unlocked for " + discountCategories.join(" and ") + "!";
} else {
msgElem.textContent = "Don't miss your discounts for " + discountCategories.slice(0, -1).join(", ") + " and " + discountCategories.slice(-1) + "!";
}
return;
}
const totalElectrons = selectionList.reduce((sum, it) => sum + it.electronCount, 0);
if (totalElectrons === 0) {
msgElem.textContent = "Select items to unlock more electrons!";
return;
}
const needed = nextOrbitalNeed(totalElectrons);
const allItems = [...baseCourses, ...baseProducts];
if (planAddons[selectedPlan]) {
allItems.push(planAddons[selectedPlan]);
}
const availableElectrons = allItems.filter(item => !selectionList.find(sel => sel.name === item.name))
.reduce((sum, item) => sum + item.electronCount, 0);
if (needed > 0 && availableElectrons >= needed) {
msgElem.textContent = `You need ${needed} more electron${needed === 1 ? '' : 's'} to fill the next orbital.`;
} else if (needed === 0) {
msgElem.textContent = "All current orbitals are filled! Checkout when you're ready.";
} else if (!quizCompleted && !discountAdditionalCoursesUnlocked) {
msgElem.textContent = "Can you split the atom?";
} else {
msgElem.textContent = "";
}
}
function nextOrbitalNeed(total) {
const dist = distributeElectrons(total);
for (let i = 0; i < dist.length; i++) {
if (dist[i] < orbitalCapacities[i]) { return orbitalCapacities[i] - dist[i]; }
}
return 0;
}
function checkDiscounts() {
return new Promise(resolve => {
const totalElectrons = selectionList.reduce((sum, it) => sum + it.electronCount, 0);
selectionList.forEach(item => {
const threshold = discountThresholds[item.category] || 0;
if (item.discountApplied && totalElectrons < threshold) {
if (originalPrices[item.name] !== undefined) { item.priceGBP = originalPrices[item.name]; }
item.discountApplied = false;
discountUsed[item.category] = false;
updateSelectionCard(item.name);
}
});
discountMentoringAvailable = false;
discountConsultingAvailable = false;
discountProductsAvailable = false;
discountCoursesAvailable = false;
if (totalElectrons >= 18 && !discountUsed.course) {
discountCoursesAvailable = true;
discountPrompted.course = true;
}
if (totalElectrons >= 10 && !discountUsed.product) {
discountProductsAvailable = true;
discountPrompted.product = true;
}
if (totalElectrons >= 2) {
if (selectedPlan === "Student" && !discountUsed.mentoring) {
discountMentoringAvailable = true;
discountPrompted.mentoring = true;
} else if (selectedPlan !== "Student" && !discountUsed.consulting) {
discountConsultingAvailable = true;
discountPrompted.consulting = true;
}
}
applyGlowIfDiscount();
updateDiscountMessage();
// Delay the scroll a bit to ensure the discount element is rendered.
setTimeout(() => {
scrollToDiscountedItem();
}, 50);
resolve();
});
}
function scrollToDiscountedItem() {
const container = document.querySelector('.body-divider');
// Find the first selection card that has a discount sticker
const discountedCard = document.querySelector('.card-glow');
if (!container || !discountedCard) {
console.log('No discounted item or container found');
return;
}
// Get the parent selection card element (the discounted item)
const card = discountedCard.closest('.selection-card');
if (!card) return;
// Calculate the card's position relative to the container
const containerRect = container.getBoundingClientRect();
const cardRect = card.getBoundingClientRect();
const relativeOffset = cardRect.top - containerRect.top;
// Adjust scroll so that the card is centered
const scrollTarget = relativeOffset + container.scrollTop - container.clientHeight / 2 + cardRect.height / 2;
container.scrollTo({
top: scrollTarget,
behavior: 'smooth'
});
}
async function finalizeCheckout() {
// Show the loading spinner
document.getElementById("loading-spinner").style.display = "block";
// 1) Build one‑off items only
const oneOffs = selectionList.map(item => {
const priceId = item.stripePriceIds
? item.stripePriceIds[currentBilling]
: item.stripePriceId; // fallback for pure one‑time items
return { priceId, quantity: 1 };
});
// 2) Send plan _separately_
const body = {
plan: selectedPlan, // "Group" / "Student" / "Professional"
billingInterval: currentBilling, // "monthly" / "four_months" / "yearly"
quantity: enterpriseUsers, // number of seats (only for Group)
items: oneOffs, // <— no plan in here
discountCode: currentDiscountCode
};
try {
const res = await fetch('https://secure.quantalumin.com/api/v1/create-checkout-session.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const text = await res.text();
const { sessionId, error } = JSON.parse(text);
if (error) {
alert('Checkout failed: ' + error);
return;
}
// Redirect to Stripe Checkout
const { error: stripeError } = await stripe.redirectToCheckout({ sessionId });
if (stripeError) {
alert(stripeError.message);
}
} catch (err) {
alert('Error occurred during checkout: ' + err.message);
} finally {
// Hide the loading spinner after checkout is complete or failed
document.getElementById("loading-spinner").style.display = "none";
}
}
function attachAtomSingleClick() {
const svg = document.getElementById("atom-svg");
if (!svg) return;
svg.addEventListener("click", function(e) {
if (e.detail === 1) { enableRapidElectronSpin(); }
});
}
function enableRapidElectronSpin() {
const svg = document.getElementById("atom-svg");
if (!svg) return;
const orbitals = svg.querySelectorAll("g[id^='orbital-']");
orbitals.forEach((orbital, index) => {
if (!orbital.dataset.origDuration) { const durations = [20,25,30,35]; orbital.dataset.origDuration = durations[index] || 20; }
orbital.style.animationDuration = "0.2s";
});
setTimeout(() => { orbitals.forEach(orbital => { orbital.style.animationDuration = orbital.dataset.origDuration + "s"; }); }, 1000);
}
let quizTimer;
let quizInterval;
function attachAtomTripleClick() {
const svg = document.getElementById("atom-svg");
if (!svg) return;
// If the quiz has already been completed, do not reattach the triple-click listener
if (quizCompleted) return;
let clickCount = 0;
let clickTimer;
svg.addEventListener("click", function(e) {
clickCount++;
clearTimeout(clickTimer);
clickTimer = setTimeout(() => { clickCount = 0; }, 600);
if (clickCount === 3) {
clickCount = 0;
handleAtomTripleClick();
}
});
}
function handleAtomTripleClick() {
// If the quiz has already been attempted, do nothing.
if (quizCompleted) return;
// Mark quiz as attempted (regardless of outcome)
quizCompleted = true;
const svg = document.getElementById("atom-svg");
if (!svg) return;
const totalElectrons = selectionList.reduce((sum, it) => sum + it.electronCount, 0);
if (totalElectrons === 0) return;
// Increase header size for quiz view
const header = document.getElementById("header-divider");
if (header) {
header.style.height = "35vh";
const bodyDivider = document.querySelector(".body-divider");
if (bodyDivider) {
bodyDivider.style.top = (header.offsetHeight + 16) + "px";
}
}
// Animate electrons flying off
const randomAngle = () => Math.random() * 360;
svg.querySelectorAll("g[id^='orbital-']").forEach(orbital => {
orbital.querySelectorAll("circle").forEach(circle => {
const rect = circle.getBoundingClientRect();
const startX = rect.left + rect.width/2;
const startY = rect.top + rect.height/2;
const electron = document.createElement("div");
electron.className = "flying-electron";
electron.style.left = startX + "px";
electron.style.top = startY + "px";
document.body.appendChild(electron);
const angle = randomAngle();
const distance = 200 + Math.random() * 100;
const destX = startX + distance * Math.cos(angle * Math.PI/180);
const destY = startY + distance * Math.sin(angle * Math.PI/180);
setTimeout(() => {
electron.style.left = destX + "px";
electron.style.top = destY + "px";
electron.addEventListener("transitionend", () => electron.remove());
}, 50);
const photon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
photon.classList.add("flying-photon");
photon.setAttribute("viewBox", "0 0 10 10");
photon.innerHTML = `<path d="M0,5 Q2.5,0 5,5 T10,5" stroke="black" fill="none" stroke-width="1"/>`;
photon.style.position = "fixed";
photon.style.left = startX + "px";
photon.style.top = startY + "px";
photon.style.transform = `rotate(${angle}deg)`;
document.body.appendChild(photon);
setTimeout(() => {
photon.style.left = destX + "px";
photon.style.top = destY + "px";
photon.style.opacity = "0";
photon.addEventListener("transitionend", () => photon.remove());
}, 50);
});
});
// After a short delay, show the quiz
setTimeout(() => {
svg.innerHTML = `
<foreignObject x="-160" y="-160" width="320" height="320">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%; padding:0.5rem;">
<div id="quiz-timer" class="quiz-timer">05:00</div>
<p style="font-weight:bold; text-align:center; margin:4px;" class="quiz-question">Who split the atom?</p>
<div class="quiz-option" onclick="handleQuizAnswer('Rutherford')">Rutherford</div>
<div class="quiz-option" onclick="handleQuizAnswer('Einstein')">Einstein</div>
<div class="quiz-option" onclick="handleQuizAnswer('M. Curie')">M. Curie</div>
</div>
</foreignObject>
`;
startQuizTimer();
}, 1000);
}
function startQuizTimer() {
let timeLeft = 300;
const timerElem = document.getElementById("quiz-timer");
if (!timerElem) return;
quizInterval = setInterval(() => {
timeLeft--;
const minutes = String(Math.floor(timeLeft / 60)).padStart(2, '0');
const seconds = String(timeLeft % 60).padStart(2, '0');
timerElem.textContent = `${minutes}:${seconds}`;
if (timeLeft <= 0) {
clearInterval(quizInterval);
timerElem.textContent = "Time expired!";
setTimeout(() => {
timerElem.parentElement.innerHTML = `<p style="font-size:1rem; font-weight:bold; text-align:center;">False!</p>`;
}, 1000);
}
}, 1000);
}
function handleQuizAnswer(answer) {
clearInterval(quizInterval);
const svg = document.getElementById("atom-svg");
if (!svg) return;
let resultMsg = "";
if (answer === "Rutherford") {
// Only unlock the discount if the quiz is passed.
discountAdditionalCoursesUnlocked = true;
quizCompleted = true; // Prevent further quiz attempts
discountSpecificCourseType = "Entrepreneurship";
resultMsg = "Correct! Discount unlocked on Entrepreneurship courses.";
} else {
resultMsg = "False!";
}
svg.innerHTML = `<foreignObject x="-80" y="-80" width="160" height="160">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%; background:#fff; padding:0.5rem;">
<p style="font-size:1rem; font-weight:bold; text-align:center;">${resultMsg}</p>
</div>
</foreignObject>`;
setTimeout(() => {
// Reset header size, update the SVG, and update the discount message.
const header = document.getElementById("header-divider");
if (header) header.style.height = "25vh";
const bodyDivider = document.querySelector(".body-divider");
if (bodyDivider && header) bodyDivider.style.top = "25vh";
updateAtomSVG();
updateDiscountMessage();
}, 2000);
}
document.addEventListener("DOMContentLoaded", () => {
const header = document.querySelector('.header-divider');
const footer = document.querySelector('.footer-divider');
const bodyDivider = document.querySelector('.body-divider');
if (header) {
header.addEventListener('wheel', function(e) {
e.preventDefault();
if (bodyDivider) bodyDivider.scrollTop += e.deltaY;
});
}
if (footer) {
footer.addEventListener('wheel', function(e) {
e.preventDefault();
if (bodyDivider) bodyDivider.scrollTop += e.deltaY;
});
}
const paymentTable = document.getElementById("payment-table");
if (paymentTable) {
// Set initial opacity to 0
paymentTable.style.opacity = 0;
// Duration of the fade-in in milliseconds
const duration = 1500;
let startTime = null;
// Animation function using requestAnimationFrame
function fadeIn(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
// Calculate the new opacity based on elapsed time
const newOpacity = Math.min(elapsed / duration, 1);
paymentTable.style.opacity = newOpacity;
if (elapsed < duration) {
requestAnimationFrame(fadeIn);
}
}
// Start the fade-in animation after a 0.5s delay
setTimeout(() => {
requestAnimationFrame(fadeIn);
}, 500);
// Scroll into view after a short delay (if needed)
setTimeout(() => {
paymentTable.scrollIntoView({ behavior: "smooth" });
}, 100);
}
});
document.addEventListener("DOMContentLoaded", () => {
createPaymentTable();
const starfield = document.getElementById('starfield');
if (starfield) {
// Move the canvas to the body (if needed) so it's not confined to .body-divider
document.body.appendChild(starfield);
// Change its style to cover the full viewport
starfield.style.position = 'absolute';
starfield.style.top = '0';
starfield.style.left = '0';
const fullPageHeight = document.documentElement.scrollHeight;
const footerHeight = document.querySelector('footer').offsetHeight;
starfield.style.height = (fullPageHeight - footerHeight) + 'px';
starfield.style.width = '100%';
starfield.style.zIndex = '-1';
}
});
/**
* Sum plan + all selected items, convert to selectedCurrency,
* and return in the smallest unit (e.g. cents for USD/EUR, pence for GBP).
*/
function calculateTotalAmountInCents() {
// 1) Plan base price
const plan = basePlans.find(p => p.name === selectedPlan);
let planGBP = 0;
if (plan) {
// base GBP price depending on billing cycle + per‑user for Groups
planGBP = (plan.name === "Group")
? plan.pricingGBP[currentBilling] + enterpriseUsers * (plan.perUserGBP || 0)
: plan.pricingGBP[currentBilling];
// apply half‑off code if active
if (discountCodeActive) planGBP *= 0.5;
}
// 2) Sum all selected items (their .priceGBP already reflects any per‑item discount)
const itemsGBP = selectionList.reduce((sum, item) => sum + item.priceGBP, 0);
// 3) Total in GBP
const totalGBP = planGBP + itemsGBP;
// 4) Convert into selectedCurrency smallest unit
// convertToCurrency: GBP → selectedCurrency
const totalInCurrency = convertToCurrency(totalGBP);
// 5) Round to nearest cent/pence and return integer
return Math.round(totalInCurrency * 100);
}
/* ---------- Toggle Courses Submenu ---------- */
function toggleCoursesSubmenu() {
const submenu = document.getElementById("courses-submenu");
const indicator = document.getElementById("courses-indicator");
if (!submenu || !indicator) return;
if (submenu.style.display === "block") {
submenu.style.display = "none";
indicator.textContent = "▾";
} else {
submenu.style.display = "block";
indicator.textContent = "▴";
}
}
document.addEventListener("click", function(e) {
const clickedLink = e.target.closest('#navbar a');
if (clickedLink && window.innerWidth <= 600) {
// If this link is inside a "has-submenu" li and it's the toggle (not one of its submenu items), skip closing
const parentLI = clickedLink.closest("li.has-submenu");
if (parentLI && clickedLink.getAttribute("href") === "javascript:void(0);") {
return; // Don't close the menu for the Courses toggle
}
// Otherwise, close the mobile navbar
document.getElementById("navbar").style.display = "none";
}
});
</script>
<canvas id="starfield" width="1137" height="1924" data-engine="three.js r167dev"></canvas>
<script type="module" src="/js/starfield.js"></script>