Data Processing Addendum

Effective Date: May 14, 2018

This Oryxcloud Data Processing Addendum forms part of, and is subject to the provisions of, the Oryxcloud Terms of Service. Capitalized terms that are not defined in this Data Processing Addendum have the meanings set forth in the Terms of Service.

1. Additional Definitions.

The following definitions apply solely to this Data Processing Addendum:

  1. the terms “controller”, “data subject”, “personal data”, “process,” “processing” and “processor” have the meanings given to these terms in EU Data Protection Law.
  2. “Breach” means a breach of the Security Measures resulting in access to Oryxcloud’s equipment or facilities storing Your Controlled Data and the accidental or unlawful destruction, loss, alteration, unauthorized disclosure of, or access to, Your Controlled Data transmitted, stored or processed by Oryxcloud on your behalf and instructions through the Services.
  3. “Content” means your User Content and any content provided to us from your End Users, including without limitation text, photos, images, audio, video, code, and any other materials.
  4. “EU Data Protection Law” means any data protection or data privacy law or regulation of Switzerland or any European Economic Area (“EEA”) country applicable to Your Controlled Data, including, as applicable, the GDPR and the e-Privacy Directive 2002/58/EC.
  5. “GDPR” means the EU General Data Protection Regulation 2016/679.
  6. “Security Measures” means the technical and organizational security measures set out.
  7. “Sub-Processor” means an entity engaged by Oryxcloud to process Your Controlled Data.
  8. “Your Controlled Data” means the personal data in the Content Oryxcloud processes on your behalf and instructions as part of the Services, but only to the extent that you are subject to EU Data Protection Law in respect of such personal data. Your Controlled Data does not include personal data when controlled by us, including without limitation data we collect (including IP address, device/browser details and web pages visited prior to coming to Your Site) with respect to your End Users’ interactions with Your Site through their browser and technologies like cookies.
2. Applicability.

This Data Processing Addendum only applies to you if you or your End Users are data subjects located within the EEA or Switzerland and only applies in respect of Your Controlled Data. You agree that Oryxcloud is not responsible for personal data that you have elected to process through Third Party Services or outside of the Services, including the systems of any other third-party cloud services, offline or on-premises storage.

3. Details of Data Processing.
  1. Subject Matter : The subject matter of the data processing under this Data Processing Addendum is Your Controlled Data.
  2. Duration : As between you and us, the duration of the data processing under this Data Processing Addendum is determined by you.
  3. Purpose : The purpose of the data processing under this Data Processing Addendum is the provision of the Services initiated by you from time to time.
  4. Nature of the Processing : The Services as described in the Agreement and initiated by you from time to time.
  5. Type of Personal Data : Your Controlled Data relating to you, your End Users or other individuals whose personal data is included in Content which is processed as part of the Services in accordance with instructions given through your Account.
  6. Categories of Data Subjects : You, Your End Users and any other individuals whose personal data is included in Content.
4. Processing Roles and Activities.
  1. Oryxcloud as Processor and You as Controller : You are the controller and Oryxcloud is the processor of Your Controlled Data.
  2. Oryxcloud as Controller : Oryxcloud may also be an independent controller for some personal data relating to you or your End Users. Please see our Privacy Policy and Terms of Service for details about this personal data which we control. We decide how to use and process that personal data independently and use it for our own purposes. When we process personal data as a controller, you acknowledge and confirm that the Agreement does not create a joint-controller relationship between you and us. If we provide you with personal data controlled by us, such as in any access to data regarding your End Users’ interactions with Your Site, you receive that as an independent data controller and are responsible for compliance with EU Data Protection Law in that regard.
  3. Description of Processing Activities : We will process Your Controlled Data for the purpose of providing you with the Services, as may be used, configured or modified from within your Account (the “Purpose”). For example, depending on how you use the Services, we may process Your Controlled Data in order to: (a) enable you to integrate content or features from a social media platform on Your Site; or (b) email your End Users on your behalf.
  4. Compliance with Laws : You will ensure that your instructions comply with all laws, regulations and rules applicable in relation to Your Controlled Data and that Your Controlled Data is collected lawfully by you or on your behalf and provided to us by you in accordance with such laws, rules and regulations. You will also ensure that the processing of Your Controlled Data in accordance with your instructions will not cause or result in us or you breaching any laws, rules or regulations (including EU Data Protection Law). You are responsible for reviewing the information available from us relating to data security pursuant to the Agreement and making an independent determination as to whether the Services meet your requirements and legal obligations as well as your obligations under this Data Processing Addendum. Oryxcloud will not access or use Your Controlled Data except as provided in the Agreement, as necessary to maintain or provide the Services or as necessary to comply with the law or binding order of a governmental, law enforcement or regulatory body.
5. Our Processing Responsibilities.
  1. How We Process : We will process Your Controlled Data for the Purpose and in accordance with the Agreement or instructions you give us through your Account. You agree that the Agreement and the instructions given through your Account are your complete and final documented instructions to us in relation to your Controlled Data. Additional instructions outside the scope of this Data Processing Addendum require prior written agreement between you and us, including agreement on any additional fees payable by you to us for carrying out such instructions. We will promptly inform you if, in our opinion, your instructions infringe applicable EU Data Protection Law, or if we are unable to comply with your instructions. We will notify you when applicable laws prevent us from complying with your instructions, except if such disclosure is prohibited by applicable law on important grounds of public interest, such as a prohibition under law to preserve the confidentiality of a law enforcement investigation or request.
  2. Notification of Breach : We will provide you notice without undue delay after becoming aware of and confirming the occurrence of a Breach for which notification to you is required under applicable EU Data Protection Laws. We will, to assist you in complying with your notification obligations under Articles 33 and 34 of the GDPR, provide you with such information about the Breach as we are reasonably able to disclose to you, taking into account the nature of the Services, the information available to us and any restrictions on disclosing the information such as for confidentiality. Our obligation to report or respond to a Breach under this Section is not and will not be construed as an acknowledgement by Oryxcloud of any fault or liability of Oryxcloud with respect to the Breach. Despite the foregoing, Oryxcloud’s obligations under this Section do not apply to incidents that are caused by you, any activity on your Account and/or Third-Party Services.
  3. Notification of Inquiry or Complaint : We will provide you notice, if permitted by applicable law, upon receiving an inquiry or complaint from an End User, or other individual whose personal data is included in your Content, or a binding demand (such as a court order or subpoena) from a government, law enforcement, regulatory or other body in respect of Your Controlled Data that we process on your behalf and instructions.
  4. Reasonable Assistance with Compliance : We will, to the extent that you cannot reasonably do so through the Services, your Account or otherwise, provide reasonable assistance to you in respect of your fulfillment of your obligation as controller to respond to requests by data subjects under Chapter 3 of the GDPR, taking into account the nature of the Services and information available to us. You will be responsible for our reasonable costs arising from our provision of such assistance.
  5. Security Measures : We will maintain the Security Measures. We may change these Security Measures but will not do so in a way that adversely affects the security of Your Controlled Data. We will take steps to ensure that any natural person acting under our authority who has access to Your Controlled Data does not process it except on our instructions, unless such person is required to do so under applicable law, and that personnel authorized by us to process Your Controlled Data have committed themselves to relevant confidentiality obligations or are under an appropriate statutory obligation of confidentiality.
  6. Sub-Processors : You agree that we can share Your Controlled Data with Sub-Processors in order to provide you the Services. We will impose contractual obligations on our Sub-Processors, and contractually obligate our Sub-Processors to impose contractual obligations on any further sub-contractors which they engage to process Your Controlled Data, which provide the same level of data protection for Your Controlled Data in all material respects as the contractual obligations imposed in this Data Processing Addendum, to the extent applicable to the nature of the Services provided by such Sub-Processor. A list of our current Sub-Processors is available upon request by sending an email to privacy@oryxcloud.com. Provided that your objection is reasonable and related to data protection concerns, you may object to any Sub-Processor by sending an email to privacy@oryxcloud.com. If you object to any Sub-Processor and your objection is reasonable and related to data protection concerns, we will use commercially reasonable efforts to make available to you a means of avoiding the processing of Your Controlled Data by the objected-to Sub-Processor. If we are unable to make available such suggested change within a reasonable period of time, we will notify you and if you still object to our use of such Sub-Processor, you may cancel or terminate your Account or, if possible, the portions of the Services that involve use of such Sub-Processor. Except as set forth in this Section 5.6, if you object to any Sub-Processors, you may not use or access the Services. You consent to our use of Sub-Processors as described in this Section 5.6. Except as set forth in this Section 5.6 or as you may otherwise authorize, we will not permit any Sub-Processor to access Your Controlled Data. Oryxcloud will remain responsible for its compliance with the obligations of this Data Processing Addendum and for any acts or omissions of any Sub-Processor or their further sub-contractors that process Your Controlled Data and cause Oryxcloud to breach any of Oryxcloud’s obligations under this Data Processing Addendum, solely to the extent that Oryxcloud would be liable under the Agreement if the act or omission was Oryxcloud’s own.
  7. Oryxcloud Audits : Oryxcloud may (but is not obliged to) use external or internal auditors to verify the adequacy of our Security Measures.
  8. Customer Audits and Information Requests : You agree to exercise any right you may have to conduct an audit or inspection by instructing Oryxcloud to carry out the audit described in Section 5.7. You agree that you may be required to agree to a non-disclosure agreement with Oryxcloud before we share any such report or outcome from such audit with you and that we may redact any such reports as we consider appropriate. If Oryxcloud does not follow such instruction or if it is legally mandatory for you to demonstrate compliance with EU Data Protection Law by means other than reviewing a report from such an audit, you may only request a change in the following way:
    1. First, submit a request for additional information in writing to Oryxcloud, specifying all details required to enable Oryxcloud to review this request effectively, including without limitation the information being requested, what form you need to obtain it in and the underlying legal requirement for the request (the “Request”). You agree that the Request will be limited to information regarding our Security Measures.
    2. Within a reasonable time after we have received and reviewed the Request, you and we will discuss and work in good faith towards agreeing on a plan to determine the details of how the Request can be addressed. You and we agree to use the least intrusive means for Oryxcloud to verify Oryxcloud’s compliance with the Security Measures in order to address the Request, taking into account applicable legal requirements, information available to or that may be provided to you, the urgency of the matter and the need for Oryxcloud to maintain uninterrupted business operations and the security of its facilities and protect itself and its customers from risk and to prevent disclosure of information that could jeopardize the confidentiality of Oryxcloud or our users’ information.
    You will pay our costs in considering and addressing any Request. Any information and documentation provided by Oryxcloud or its auditors pursuant to this Section 5.8 will be provided at your cost. If we decline to follow any instruction requested by you regarding audits or inspections, you may cancel any affected Paid Services.
  9. Questions : Upon your reasonable requests to us for information regarding our compliance with the obligations set forth in this Data Processing Addendum, we shall, where such information is not otherwise available to you, provide you with written responses, provided that you agree not to exercise this right more than one (1) time per calendar year (unless it is necessary for you to do so to comply with EU Data Protection Law). The information to be made available by Oryxcloud under this Section 5.9 is limited to solely that information necessary, taking into account the nature of the Services and the information available to Oryxcloud, to assist you in complying with your obligations under the GDPR in respect of data protection impact assessments and prior consultation. You agree that you may be required to agree to a non-disclosure agreement with Oryxcloud before we share any such information with you.
  10. Requests : You can delete or access a copy of some of Your Controlled Data through your Account. For any of Your Controlled Data which may not be deleted or accessed through your Account, upon your written request, we will, with respect to any of Your Controlled Data in our or our Sub-Processor’s possession that we can associate with a data subject, subject to the limitations described in the Agreement and unless prohibited by applicable law or the order of a governmental, law enforcement or regulatory body: (a) return such data and copies of such data to you provided that you make such request within no more than ninety (90) days after the cancellation of the applicable Paid Services; or (b) delete, and request that our Sub-Processors delete, such data (excluding in the case of (a) or (b) any of such data which is archived on back-up systems, which we shall securely isolate and protect from any further processing, except to the extent required by applicable law). Otherwise, we will delete Your Controlled Data in accordance with our data retention policy. This Section 5.10 does not apply to personal data held by Third Party Services.
6. Data Transfers

You authorize us to transfer Your Controlled Data away from the country in which such data was originally collected. In particular, you authorize us to transfer Your Controlled Data to the US. We will transfer Your Controlled Data to outside the EEA using the Swiss-U.S. and EU-U.S. Privacy Shield Frameworks or another lawful data transfer mechanism that is recognized under EU Data Protection Law as providing an adequate level of protection for such data transfers.

7. Liability

The liability of each party under this Data Processing Addendum is subject to the exclusions and limitations of liability set out in the Agreement. You agree that any regulatory penalties or claims by data subjects or others incurred by Oryxcloud in relation to Your Controlled Data that arise as a result of, or in connection with, your failure to comply with your obligations under this Data Processing Addendum or EU Data Protection Law shall reduce Oryxcloud’s maximum aggregate liability to you under the Agreement in the same amount as the fine and/or liability incurred by us as a result.

8. Conflict

In the event of a conflict between this Data Processing Addendum and the Terms of Service, this Data Processing Addendum will control.

9. Miscellaneous

You are responsible for any costs and expenses arising from Oryxcloud’s compliance with your instructions or requests pursuant to the Agreement (including this Data Processing Addendum) which fall outside the standard functionality made available by Oryxcloud generally through the Services.

// LeadCenter.ai Quiz Application JavaScript class QuizApp { constructor() { this.currentStep = 0; this.totalSteps = 0; this.answers = {}; this.correctAnswerTriggered = false; this.currentQuestionAnsweredCorrectly = false; this.questions = [ // Personal Details Questions { id: 1, question: "Let's start with some basic information about you and your company", type: "personal_details", fields: [ { name: "name", label: "Name", type: "text", required: true, placeholder: "Enter your full name" }, { name: "email", label: "Email Address", type: "email", required: true, placeholder: "Enter your email address" }, { name: "phone", label: "Phone Number", type: "tel", required: true, placeholder: "Enter your phone number" }, { name: "company", label: "Company Name", type: "text", required: true, placeholder: "Enter your company name" }, { name: "employees", label: "Number of Employees", type: "select", required: true, options: [ "1-5", "6-10", "11-20", "20-50", "More than 50 employees" ] } ] }, // Business Questions { id: 2, question: "Which of the following AE Advisors use LeadCenter.AI?", options: [ "Oak Harvest Financial Group", "Howard Bailey", "West Advisory Group", "CA Educators", "Legato Financials", "Slagle Financials", "Freedom Financials", "Comprehensive Advisors", "Jehm Wealth", "Wealth Planning Network", ], type: "multiple" }, { id: 3, question: "Which challenges does LeadCenter.AI solve for financial advisors?", options: [ "Reducing manual data entry", "Breaking silos between marketing, sales, and operations", "Automating tasks to save valuable time", "Engaging more leads, faster", "Boosting lead-to-appointment conversions", "Driving higher revenue", "Simplifying reports and insights", ], type: "multiple" }, { id: 4, question: "Which of the following functions does LeadCenter.AI automate for financial advisors?", options: [ "Capturing leads from all sources (forms, phone calls, seminar registrations)", "Pushing leads to Redtail, Wealthbox, and Salesforce", "Verifying lead phone numbers and emails", "Transcribing calls and attaching them to contact records", "Creating landing pages and seminar forms on the advisor's website", "Sending client review reminders so clients can book their own appointments", "Producing advisor-specific reports (production reports, 1st appointment conversion, ROI, customer acquisition cost, etc.)", "Capturing notes & tasks from Zoom calls under the contact record", "Lead engagement in the sales funnel by triggering emails & texts based on lead criteria" ], type: "multiple" }, { id: 5, question: "How much can an RIA with 5 employees save, boost revenue, and cut software licensing costs by using LeadCenter.AI?", options: [ "Save $10K annually, does not boost revenue, and not cut in software costs", "Save $100K annually, boost revenue by 1x, and cut software costs by $5K", "Save $223K annually by reclaiming up to 434 workdays, boost revenue by up to 3x, and cut software licensing costs by up to $22K" ], type: "single" } ]; // Banner messages for each question this.bannerMessages = { 1: { success: "That's the correct answer! Great job!", wrong: "That is a wrong answer, please try again" }, 2: { success: "Correct! Well done! Today, more than 1,000 advisors rely on LeadCenter.AI. The platform achieved 950% year-over-year growth last year—clear proof of the value it delivers to advisors", wrong: "That is a wrong answer, please try again" }, 3: { success: "That’s correct! LeadCenter.AI was designed in collaboration with financial advisors, built around their feedback to directly address these challenges", wrong: "That is a wrong answer, please try again" }, 4: { success: "That’s correct! LeadCenter.AI streamlines over 400 processes, empowering financial advisors to save time and focus on their clients.", wrong: "That is a wrong answer, please try again" }, 5: { success: "That’s correct! With LeadCenter.AI, advisors eliminate manual work, freeing up valuable time to focus on driving revenue and serving their clients.", wrong: "That is a wrong answer, please try again" } }; this.init(); } init() { this.totalSteps = this.questions.length; this.renderQuestions(); this.updateProgress(); } renderQuestions() { const container = document.querySelector('.question-container'); container.innerHTML = ''; this.questions.forEach((question, index) => { const questionElement = this.createQuestionElement(question, index); container.appendChild(questionElement); }); } createQuestionElement(question, index) { const questionDiv = document.createElement('div'); questionDiv.className = `question-step ${index === 0 ? 'active' : ''}`; questionDiv.id = `question-${question.id}`; questionDiv.style.display = index === 0 ? 'block' : 'none'; const questionCard = document.createElement('div'); questionCard.className = 'question-card'; const questionTitle = document.createElement('h3'); questionTitle.className = 'question-title'; questionTitle.textContent = question.question; questionCard.appendChild(questionTitle); if (question.type === 'personal_details') { // Create form for personal details const formContainer = document.createElement('div'); formContainer.className = 'personal-details-form'; question.fields.forEach(field => { const fieldGroup = document.createElement('div'); fieldGroup.className = 'form-group mb-3'; const label = document.createElement('label'); label.className = 'form-label'; label.textContent = field.label; if (field.required) { label.innerHTML += ' *'; } let input; if (field.type === 'select') { input = document.createElement('select'); input.className = 'form-control'; input.name = `question-${question.id}-${field.name}`; input.id = `q${question.id}-${field.name}`; input.required = field.required; // Add default option const defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = `Select ${field.label.toLowerCase()}`; input.appendChild(defaultOption); // Add options field.options.forEach(option => { const optionElement = document.createElement('option'); optionElement.value = option; optionElement.textContent = option; input.appendChild(optionElement); }); } else { input = document.createElement('input'); input.type = field.type; input.className = 'form-control'; input.name = `question-${question.id}-${field.name}`; input.id = `q${question.id}-${field.name}`; input.placeholder = field.placeholder; input.required = field.required; } fieldGroup.appendChild(label); fieldGroup.appendChild(input); // Add email validation for email field (after input is added to fieldGroup) if (field.type === 'email') { this.addEmailValidation(input, fieldGroup); } // Add phone formatting for phone field if (field.type === 'tel') { this.addPhoneFormatting(input); } // Add event listener to update Next button state for personal details if (question.type === 'personal_details') { input.addEventListener('input', () => { this.updateNavigationButtons(); }); input.addEventListener('change', () => { this.updateNavigationButtons(); }); } formContainer.appendChild(fieldGroup); }); questionCard.appendChild(questionTitle); questionCard.appendChild(formContainer); } else { // Create options for regular questions const optionsList = document.createElement('ul'); optionsList.className = 'question-options'; question.options.forEach((option, optionIndex) => { const optionItem = document.createElement('li'); optionItem.className = 'question-option'; const input = document.createElement('input'); input.type = question.type === 'multiple' ? 'checkbox' : 'radio'; input.className = 'option-input'; input.name = `question-${question.id}`; input.value = option; input.id = `q${question.id}-option${optionIndex}`; // Add event listener for answer detection (correct or wrong) input.addEventListener('change', (e) => { if (question.id === 2) { // Special handling for question 2 - multiple choice, all options must be selected this.checkQuestion2Completion(); } else if (question.id === 3) { // Special handling for question 3 - multiple choice, all options must be selected this.checkQuestion3Completion(); } else if (question.id === 4) { // Special handling for question 4 - multiple choice, all options must be selected this.checkQuestion4Completion(); } else { // Regular single choice questions if (e.target.checked) { // Check if this is a correct answer if (question.id === 5 && option === 'Save $223K annually by reclaiming up to 434 workdays, boost revenue by up to 3x, and cut software licensing costs by up to $22K') { // Correct answer console.log('Correct answer selected!', e.target.checked); this.currentQuestionAnsweredCorrectly = true; if (!this.correctAnswerTriggered) { this.correctAnswerTriggered = true; this.hideWrongAnswerBanner(); this.showSuccessBanner(); this.playFireworks(); } // Don't update navigation buttons - let popup handle progression } else { // Wrong answer console.log('Wrong answer selected!', option); this.currentQuestionAnsweredCorrectly = false; this.hideSuccessBanner(); this.showWrongAnswerBanner(); this.updateNavigationButtons(); } } } }); const label = document.createElement('label'); label.className = 'option-label'; label.htmlFor = `q${question.id}-option${optionIndex}`; label.textContent = option; // Add click listener to label for answer detection label.addEventListener('click', () => { setTimeout(() => { if (question.id === 2) { // Special handling for question 2 - multiple choice, all options must be selected this.checkQuestion2Completion(); } else if (question.id === 3) { // Special handling for question 3 - multiple choice, all options must be selected this.checkQuestion3Completion(); } else if (question.id === 4) { // Special handling for question 4 - multiple choice, all options must be selected this.checkQuestion4Completion(); } else { // Regular single choice questions if (input.checked) { // Check if this is a correct answer if (question.id === 5 && option === 'Save $223K annually by reclaiming up to 434 workdays, boost revenue by up to 3x, and cut software licensing costs by up to $22K') { // Correct answer console.log('Label clicked for correct answer'); this.currentQuestionAnsweredCorrectly = true; if (!this.correctAnswerTriggered) { console.log('Input is checked, triggering effects'); this.correctAnswerTriggered = true; this.hideWrongAnswerBanner(); this.showSuccessBanner(); this.playFireworks(); } // Don't update navigation buttons - let popup handle progression } else { // Wrong answer console.log('Label clicked for wrong answer'); this.currentQuestionAnsweredCorrectly = false; this.hideSuccessBanner(); this.showWrongAnswerBanner(); this.updateNavigationButtons(); } } } }, 100); }); optionItem.appendChild(input); optionItem.appendChild(label); optionsList.appendChild(optionItem); }); questionCard.appendChild(optionsList); // Add hint for specific questions (after options) // Hint removed as requested } questionDiv.appendChild(questionCard); return questionDiv; } updateProgress() { const progressBar = document.getElementById('progress-bar'); const progressText = document.getElementById('progress-text'); const percentage = ((this.currentStep + 1) / this.totalSteps) * 100; progressBar.style.width = `${percentage}%`; progressText.textContent = `Question ${this.currentStep + 1} of ${this.totalSteps}`; } showQuestion(step) { // Hide all questions document.querySelectorAll('.question-step').forEach(question => { question.style.display = 'none'; question.classList.remove('active'); }); // Show current question const currentQuestion = document.getElementById(`question-${this.questions[step].id}`); if (currentQuestion) { currentQuestion.style.display = 'block'; currentQuestion.classList.add('active'); } // Reset flags for new question this.correctAnswerTriggered = false; this.currentQuestionAnsweredCorrectly = false; // Hide any existing banners this.hideSuccessBanner(); this.hideWrongAnswerBanner(); // Update navigation buttons this.updateNavigationButtons(); // Scroll to top of the page window.scrollTo({ top: 0, behavior: 'smooth' }); } updateNavigationButtons() { const prevBtn = document.getElementById('prev-btn'); const nextBtn = document.getElementById('next-btn'); // Show/hide previous button if (this.currentStep === 0) { prevBtn.style.display = 'none'; } else { prevBtn.style.display = 'block'; } // Update next button text if (this.currentStep === this.totalSteps - 1) { nextBtn.innerHTML = 'Submit Quiz'; } else { nextBtn.innerHTML = 'Next'; } // Disable Next button if current question hasn't been answered correctly const currentQuestion = this.questions[this.currentStep]; if (currentQuestion && currentQuestion.type === 'personal_details') { // For personal details, check if all required fields are filled const allFieldsFilled = this.validateCurrentQuestion(); if (allFieldsFilled) { nextBtn.disabled = false; nextBtn.classList.remove('btn-disabled'); } else { nextBtn.disabled = true; nextBtn.classList.add('btn-disabled'); } } else if (currentQuestion && (currentQuestion.id === 2 || currentQuestion.id === 3 || currentQuestion.id === 4)) { // For questions 2, 3, and 4, enable when all options are selected if (this.currentQuestionAnsweredCorrectly) { nextBtn.disabled = false; nextBtn.classList.remove('btn-disabled'); } else { nextBtn.disabled = true; nextBtn.classList.add('btn-disabled'); } } else { // For other quiz questions, disable until correct answer is selected if (this.currentQuestionAnsweredCorrectly) { nextBtn.disabled = false; nextBtn.classList.remove('btn-disabled'); } else { nextBtn.disabled = true; nextBtn.classList.add('btn-disabled'); } } } saveAnswer() { const currentQuestion = this.questions[this.currentStep]; const questionId = currentQuestion.id; if (currentQuestion.type === 'personal_details') { this.answers[questionId] = {}; currentQuestion.fields.forEach(field => { const input = document.querySelector(`input[name="question-${questionId}-${field.name}"], select[name="question-${questionId}-${field.name}"]`); this.answers[questionId][field.name] = input ? input.value : ''; }); } else if (currentQuestion.type === 'multiple') { const checkboxes = document.querySelectorAll(`input[name="question-${questionId}"]:checked`); this.answers[questionId] = Array.from(checkboxes).map(cb => cb.value); } else { const radio = document.querySelector(`input[name="question-${questionId}"]:checked`); this.answers[questionId] = radio ? radio.value : null; } } loadAnswer() { const currentQuestion = this.questions[this.currentStep]; const questionId = currentQuestion.id; const savedAnswer = this.answers[questionId]; if (savedAnswer) { if (currentQuestion.type === 'personal_details') { currentQuestion.fields.forEach(field => { const input = document.querySelector(`input[name="question-${questionId}-${field.name}"], select[name="question-${questionId}-${field.name}"]`); if (input && savedAnswer[field.name]) { input.value = savedAnswer[field.name]; } }); } else if (currentQuestion.type === 'multiple') { savedAnswer.forEach(answer => { const checkbox = document.querySelector(`input[name="question-${questionId}"][value="${answer}"]`); if (checkbox) checkbox.checked = true; }); } else { const radio = document.querySelector(`input[name="question-${questionId}"][value="${savedAnswer}"]`); if (radio) radio.checked = true; } } } validateCurrentQuestion() { const currentQuestion = this.questions[this.currentStep]; const questionId = currentQuestion.id; if (currentQuestion.type === 'personal_details') { // Check all required fields are filled for (let field of currentQuestion.fields) { if (field.required) { const input = document.querySelector(`input[name="question-${questionId}-${field.name}"], select[name="question-${questionId}-${field.name}"]`); if (!input || !input.value.trim()) { return false; } // Validate email format if it's an email field if (field.type === 'email') { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; if (!emailRegex.test(input.value.trim())) { return false; } } } } return true; } else if (currentQuestion.type === 'multiple') { if (questionId === 2 || questionId === 3 || questionId === 4) { // For questions 2, 3, and 4, only allow progression if all options are selected const allInputs = document.querySelectorAll(`input[name="question-${questionId}"]`); const checkedInputs = document.querySelectorAll(`input[name="question-${questionId}"]:checked`); return checkedInputs.length === allInputs.length; } else { // For other multiple choice questions, just check if at least one is selected const checkboxes = document.querySelectorAll(`input[name="question-${questionId}"]:checked`); return checkboxes.length > 0; } } else { const radio = document.querySelector(`input[name="question-${questionId}"]:checked`); return radio !== null; } } nextQuestion() { if (!this.validateCurrentQuestion()) { const currentQuestion = this.questions[this.currentStep]; // Don't show validation messages for questions 2, 3, and 4 if (currentQuestion.id === 2 || currentQuestion.id === 3 || currentQuestion.id === 4) { return; } let message = 'Please complete all required fields before proceeding.'; if (currentQuestion.type === 'personal_details') { message = 'Please fill in all required fields and check your email format.'; } else if (currentQuestion.type === 'multiple') { message = 'Please select at least one option before proceeding.'; } else { message = 'Please select an answer before proceeding.'; } this.showValidationMessage(message); return; } this.saveAnswer(); if (this.currentStep < this.totalSteps - 1) { this.currentStep++; this.showQuestion(this.currentStep); this.loadAnswer(); this.updateProgress(); } else { this.submitQuiz(); } } previousQuestion() { if (this.currentStep > 0) { this.saveAnswer(); this.currentStep--; this.showQuestion(this.currentStep); this.loadAnswer(); this.updateProgress(); } } showValidationMessage(message) { // Remove existing validation message const existingMessage = document.querySelector('.validation-message'); if (existingMessage) { existingMessage.remove(); } // Create new validation message const validationDiv = document.createElement('div'); validationDiv.className = 'alert alert-warning validation-message'; validationDiv.innerHTML = `${message}`; // Insert after current question const currentQuestion = document.querySelector('.question-step.active'); currentQuestion.appendChild(validationDiv); // Auto-remove after 3 seconds setTimeout(() => { if (validationDiv.parentNode) { validationDiv.remove(); } }, 3000); } submitQuiz() { this.saveAnswer(); // Show loading state const nextBtn = document.getElementById('next-btn'); const originalText = nextBtn.innerHTML; nextBtn.innerHTML = ' Submitting...'; nextBtn.disabled = true; // Simulate API call setTimeout(() => { this.showThankYouStep(); }, 2000); } showThankYouStep() { // Hide quiz steps document.getElementById('quiz-steps').style.display = 'none'; // Show thank you step document.getElementById('step-thankyou').style.display = 'block'; document.getElementById('step-thankyou').classList.add('active'); // Log answers for debugging console.log('Quiz Answers:', this.answers); } restartQuiz() { // Reset state this.currentStep = 0; this.answers = {}; this.correctAnswerTriggered = false; this.currentQuestionAnsweredCorrectly = false; // Hide thank you step document.getElementById('step-thankyou').style.display = 'none'; document.getElementById('step-thankyou').classList.remove('active'); // Show intro step document.getElementById('step-intro').style.display = 'block'; document.getElementById('step-intro').classList.add('active'); // Reset form document.querySelectorAll('.option-input').forEach(input => { input.checked = false; }); // Hide banners this.hideSuccessBanner(); this.hideWrongAnswerBanner(); this.updateProgress(); } showSuccessBanner() { console.log('Showing success popup...'); const popup = document.getElementById('success-popup'); if (popup) { // Get current question and update text dynamically const currentQuestion = this.questions[this.currentStep]; const message = this.bannerMessages[currentQuestion.id]?.success || "That's the correct answer! Great job!"; // Update the popup text const popupText = document.getElementById('success-popup-text'); if (popupText) { popupText.textContent = message; } // Update button text based on question number const nextButton = popup.querySelector('button[onclick="proceedToNextQuestion()"]'); if (nextButton) { if (currentQuestion.id === 5) { nextButton.innerHTML = 'Submit Quiz '; } else { nextButton.innerHTML = 'Next Question '; } } // Show the modal using Bootstrap const modal = new bootstrap.Modal(popup); modal.show(); console.log('Success popup displayed'); } else { console.error('Success popup element not found!'); } } hideSuccessBanner() { const popup = document.getElementById('success-popup'); if (popup) { const modal = bootstrap.Modal.getInstance(popup); if (modal) { modal.hide(); } } } showWrongAnswerBanner() { console.log('Showing wrong answer banner...'); const banner = document.getElementById('wrong-answer-banner'); if (banner) { // Get current question and update text dynamically const currentQuestion = this.questions[this.currentStep]; const message = this.bannerMessages[currentQuestion.id]?.wrong || "That is a wrong answer, please try again"; // Update the banner text const bannerText = banner.querySelector('strong'); if (bannerText) { bannerText.textContent = message; } banner.style.display = 'block'; console.log('Wrong answer banner displayed'); // Auto-hide after 3 seconds setTimeout(() => { this.hideWrongAnswerBanner(); }, 3000); } else { console.error('Wrong answer banner element not found!'); } } hideWrongAnswerBanner() { const banner = document.getElementById('wrong-answer-banner'); if (banner) { banner.style.display = 'none'; } } addEmailValidation(input, fieldGroup) { // Create error message element const errorElement = document.createElement('div'); errorElement.className = 'email-error text-danger small mt-1'; errorElement.style.display = 'none'; errorElement.textContent = 'Please enter a valid email address in the format name@company.com'; // Add error element after the input fieldGroup.appendChild(errorElement); // Email validation function const validateEmail = (email) => { const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; return emailRegex.test(email); }; // Add event listeners input.addEventListener('blur', () => { const email = input.value.trim(); if (email && !validateEmail(email)) { errorElement.style.display = 'block'; input.classList.add('is-invalid'); } else { errorElement.style.display = 'none'; input.classList.remove('is-invalid'); } }); input.addEventListener('input', () => { const email = input.value.trim(); if (email && !validateEmail(email)) { errorElement.style.display = 'block'; input.classList.add('is-invalid'); } else { errorElement.style.display = 'none'; input.classList.remove('is-invalid'); } }); } addPhoneFormatting(input) { // Set placeholder to show the expected format input.placeholder = '(XXX) XXX-XXXX'; // Add input event listener for formatting input.addEventListener('input', (e) => { let value = e.target.value.replace(/\D/g, ''); // Remove all non-digits // Limit to 10 digits if (value.length > 10) { value = value.substring(0, 10); } // Format the phone number let formattedValue = ''; if (value.length > 0) { if (value.length <= 3) { formattedValue = `(${value}`; } else if (value.length <= 6) { formattedValue = `(${value.substring(0, 3)}) ${value.substring(3)}`; } else { formattedValue = `(${value.substring(0, 3)}) ${value.substring(3, 6)}-${value.substring(6)}`; } } e.target.value = formattedValue; }); // Add keydown event listener to prevent non-numeric input input.addEventListener('keydown', (e) => { // Allow: backspace, delete, tab, escape, enter, home, end, left, right, up, down if ([8, 9, 27, 13, 46, 35, 36, 37, 38, 39, 40].indexOf(e.keyCode) !== -1 || // Allow: Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X (e.keyCode === 65 && e.ctrlKey === true) || (e.keyCode === 67 && e.ctrlKey === true) || (e.keyCode === 86 && e.ctrlKey === true) || (e.keyCode === 88 && e.ctrlKey === true)) { return; } // Ensure that it is a number and stop the keypress if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) { e.preventDefault(); } }); // Add paste event listener to handle pasted content input.addEventListener('paste', (e) => { e.preventDefault(); const pastedData = e.clipboardData.getData('text'); const numbersOnly = pastedData.replace(/\D/g, ''); if (numbersOnly.length <= 10) { input.value = numbersOnly; input.dispatchEvent(new Event('input')); } }); } checkQuestion2Completion() { // Check if all options for question 2 are selected const question2Inputs = document.querySelectorAll('input[name="question-2"]'); const checkedInputs = document.querySelectorAll('input[name="question-2"]:checked'); console.log(`Question 2: ${checkedInputs.length} of ${question2Inputs.length} options selected`); if (checkedInputs.length === question2Inputs.length) { // All options selected - show success popup this.currentQuestionAnsweredCorrectly = true; if (!this.correctAnswerTriggered) { this.correctAnswerTriggered = true; this.hideWrongAnswerBanner(); this.showSuccessBanner(); this.playFireworks(); } // Don't update navigation buttons - let popup handle progression } else { // Not all options selected - hide success popup but don't show error this.currentQuestionAnsweredCorrectly = false; this.hideSuccessBanner(); this.hideWrongAnswerBanner(); // Don't show error message for question 2 this.updateNavigationButtons(); } } checkQuestion3Completion() { // Check if all options for question 3 are selected const question3Inputs = document.querySelectorAll('input[name="question-3"]'); const checkedInputs = document.querySelectorAll('input[name="question-3"]:checked'); console.log(`Question 3: ${checkedInputs.length} of ${question3Inputs.length} options selected`); if (checkedInputs.length === question3Inputs.length) { // All options selected - show success popup this.currentQuestionAnsweredCorrectly = true; if (!this.correctAnswerTriggered) { this.correctAnswerTriggered = true; this.hideWrongAnswerBanner(); this.showSuccessBanner(); this.playFireworks(); } // Don't update navigation buttons - let popup handle progression } else { // Not all options selected - hide success popup but don't show error this.currentQuestionAnsweredCorrectly = false; this.hideSuccessBanner(); this.hideWrongAnswerBanner(); // Don't show error message for question 3 this.updateNavigationButtons(); } } checkQuestion4Completion() { // Check if all options for question 4 are selected const question4Inputs = document.querySelectorAll('input[name="question-4"]'); const checkedInputs = document.querySelectorAll('input[name="question-4"]:checked'); console.log(`Question 4: ${checkedInputs.length} of ${question4Inputs.length} options selected`); if (checkedInputs.length === question4Inputs.length) { // All options selected - show success popup this.currentQuestionAnsweredCorrectly = true; if (!this.correctAnswerTriggered) { this.correctAnswerTriggered = true; this.hideWrongAnswerBanner(); this.showSuccessBanner(); this.playFireworks(); } // Don't update navigation buttons - let popup handle progression } else { // Not all options selected - hide success popup but don't show error this.currentQuestionAnsweredCorrectly = false; this.hideSuccessBanner(); this.hideWrongAnswerBanner(); // Don't show error message for question 4 this.updateNavigationButtons(); } } playFireworks() { console.log('Playing confetti celebration...'); // Use confetti library for celebration if (typeof confetti !== 'undefined') { try { // Multiple confetti bursts for better effect const duration = 3000; const animationEnd = Date.now() + duration; const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 1000 }; function randomInRange(min, max) { return Math.random() * (max - min) + min; } const interval = setInterval(function() { const timeLeft = animationEnd - Date.now(); if (timeLeft <= 0) { return clearInterval(interval); } const particleCount = 50 * (timeLeft / duration); // Launch confetti from different positions confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 } }); confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 } }); }, 250); console.log('Confetti celebration started'); } catch (error) { console.error('Error with confetti:', error); this.showCelebrationFallback(); } } else { console.log('Confetti library not available, using fallback'); this.showCelebrationFallback(); } } showCelebrationFallback() { console.log('Showing celebration fallback...'); // Create a simple celebration overlay const celebration = document.createElement('div'); celebration.id = 'celebration-fallback'; celebration.style.position = 'fixed'; celebration.style.top = '0'; celebration.style.left = '0'; celebration.style.width = '100%'; celebration.style.height = '100%'; celebration.style.background = 'linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57)'; celebration.style.display = 'flex'; celebration.style.alignItems = 'center'; celebration.style.justifyContent = 'center'; celebration.style.zIndex = '9999'; celebration.style.fontSize = '3rem'; celebration.style.fontWeight = 'bold'; celebration.style.color = 'white'; celebration.style.textAlign = 'center'; celebration.style.animation = 'pulse 0.5s ease-in-out infinite alternate'; celebration.innerHTML = '🎉
CORRECT!
🎉'; document.body.appendChild(celebration); // Remove after 3 seconds setTimeout(() => { if (document.body.contains(celebration)) { document.body.removeChild(celebration); } }, 3000); } } // Global functions for button clicks let quizApp; function startQuiz() { // Hide intro step document.getElementById('step-intro').style.display = 'none'; document.getElementById('step-intro').classList.remove('active'); // Show quiz steps document.getElementById('quiz-steps').style.display = 'block'; // Scroll to top of the page window.scrollTo({ top: 0, behavior: 'smooth' }); // Initialize quiz app quizApp = new QuizApp(); } function nextQuestion() { if (quizApp) { quizApp.nextQuestion(); } } function previousQuestion() { if (quizApp) { quizApp.previousQuestion(); } } function restartQuiz() { if (quizApp) { quizApp.restartQuiz(); } } function proceedToNextQuestion() { if (quizApp) { // Hide the success popup quizApp.hideSuccessBanner(); // Proceed to next question quizApp.nextQuestion(); } } // Initialize when page loads document.addEventListener('DOMContentLoaded', function() { // Add smooth scrolling document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); document.querySelector(this.getAttribute('href')).scrollIntoView({ behavior: 'smooth' }); }); }); // Add form validation styles const style = document.createElement('style'); style.textContent = ` .validation-message { margin-top: 1rem; animation: slideDown 0.3s ease; } @keyframes slideDown { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } .option-label:focus-within { outline: 2px solid var(--primary-color); outline-offset: 2px; } `; document.head.appendChild(style); // Test function for debugging window.testFireworks = function() { console.log('Testing fireworks and banner...'); if (quizApp) { quizApp.showSuccessBanner(); quizApp.playFireworks(); } else { console.log('Quiz app not initialized yet'); } }; });