From fe3672d4dc6f1e3471239e57228fa17a8406495d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 7 Jan 2026 16:00:17 +0000 Subject: [PATCH] Feat: Add GCAP3056 course hub and chatbot page Co-authored-by: simonwang --- .../GCAP3056/govEnquiryChatbot.html | 604 +++++ gcap3056_extract/GCAP3056/intro.html | 448 ++++ .../GCAP3056/week1-3/PracticeDiscussion.html | 841 +++++++ .../GCAP3056/week1-3/ReflectAssess.html | 93 + .../GCAP3056/week1-3/css/styles.css | 163 ++ gcap3056_extract/GCAP3056/week1-3/index.html | 156 ++ .../GCAP3056/week1-3/interactiveLecture.html | 353 +++ .../GCAP3056/week1-3/js/course-app.js | 2101 +++++++++++++++++ .../GCAP3056/week12-13/index.html | 0 gcap3056_extract/GCAP3056/week4-5/index.html | 0 gcap3056_extract/GCAP3056/week6-7/index.html | 0 gcap3056_extract/GCAP3056/week8-11/index.html | 0 .../Wheeldecide/iframes/GCAP3056.html | 1 + 13 files changed, 4760 insertions(+) create mode 100644 gcap3056_extract/GCAP3056/govEnquiryChatbot.html create mode 100644 gcap3056_extract/GCAP3056/intro.html create mode 100644 gcap3056_extract/GCAP3056/week1-3/PracticeDiscussion.html create mode 100644 gcap3056_extract/GCAP3056/week1-3/ReflectAssess.html create mode 100644 gcap3056_extract/GCAP3056/week1-3/css/styles.css create mode 100644 gcap3056_extract/GCAP3056/week1-3/index.html create mode 100644 gcap3056_extract/GCAP3056/week1-3/interactiveLecture.html create mode 100644 gcap3056_extract/GCAP3056/week1-3/js/course-app.js create mode 100644 gcap3056_extract/GCAP3056/week12-13/index.html create mode 100644 gcap3056_extract/GCAP3056/week4-5/index.html create mode 100644 gcap3056_extract/GCAP3056/week6-7/index.html create mode 100644 gcap3056_extract/GCAP3056/week8-11/index.html create mode 100644 gcap3056_extract/Wheeldecide/iframes/GCAP3056.html diff --git a/gcap3056_extract/GCAP3056/govEnquiryChatbot.html b/gcap3056_extract/GCAP3056/govEnquiryChatbot.html new file mode 100644 index 00000000..0024a753 --- /dev/null +++ b/gcap3056_extract/GCAP3056/govEnquiryChatbot.html @@ -0,0 +1,604 @@ + + + + + + GCAP3056 - Government Enquiry Chatbot + + + + + + + + +
+
+
+

๐Ÿ›๏ธ Government Enquiry Chatbot

+

GCAP3056 - Taking a Stand: Turning Research Insights into Policy Recommendations

+
+
+
+ + +
+
+ +
+
+ + +
+ + +
+ + +
+
+
+ ๐Ÿ›๏ธ +
+

Welcome to Your Government Enquiry Assistant

+

Get personalized guidance for your government enquiry and policy recommendations

+
+ +
+
+

๐Ÿ“ What This Chatbot Helps With

+
    +
  • + โœ“ + Understanding government policies and decision-making processes +
  • +
  • + โœ“ + Formulating policy recommendations based on research +
  • +
  • + โœ“ + Learning how to engage with government effectively +
  • +
  • + โœ“ + Developing evidence-based arguments for policy change +
  • +
+
+
+

๐ŸŽ“ Expected Outcome

+
+

+ After your session, you'll have a clear understanding of how to engage with government on policy issues, + formulate evidence-based recommendations, and develop strategies for effective civic participation. +

+
+
+
+
+ + +
+

+ ๐Ÿš€How to Use the Chatbot +

+ +
+
+
+ ๐Ÿ”‘ +
+

Get Your HKBU API Key

+

+ Visit HKBU GenAI Settings +

+

Login โ†’ Click "GENERATE API KEY" โ†’ Save securely

+
+
+
+ 1๏ธโƒฃ +
+

Switch to Chatbot

+

Click the "๐Ÿค– Government Enquiry Chatbot" tab above to access the chatbot

+
+
+
+ 2๏ธโƒฃ +
+

Start the Conversation

+

Enter your API key, then type ok to begin

+
+
+ +
+
+
+
+ ๐Ÿ”’ +
+
+

HKBU API Key Required

+
+

Step 1: Visit genai.hkbu.edu.hk/settings/api-docs

+

Step 2: Login with your HKBU credentials

+

Step 3: Click the blue "GENERATE API KEY" button

+

Step 4: Copy and save your API key securely (do not share with others)

+

Step 5: Enter the API key in the chatbot to start your session

+
+
+
+
+
+ + +
+ +
+
+ + +
+
+
+ ๐Ÿ“ง +
+

Participation Tracking & Email Reports

+
+ +
+
+

+ ๐Ÿ“ŠParticipation Tracking +

+

+ Your chatbot interactions will be automatically tracked and counted as course participation. +

+
    +
  • + โ€ข + Each session contributes to your participation grade +
  • +
  • + โ€ข + Helps you develop your reflective learning journal +
  • +
  • + โ€ข + Provides evidence of your engagement with course material +
  • +
+
+ +
+

+ ๐Ÿ“จEmail Reports +

+

+ After each session, you'll receive a detailed report via email. +

+
    +
  • + โ€ข + Summary of your conversation sent to your email +
  • +
  • + โ€ข + Teacher receives a copy for assessment purposes +
  • +
  • + โ€ข + Use reports to write your reflective learning journal +
  • +
+
+
+ +
+
+

+ ๐Ÿ’ก Remember: Make sure to use your HKBU email address when prompted. + This ensures you receive your participation reports and helps with course assessment. +

+
+
+
+ + +
+

+ ๐Ÿ’กTips for Effective Government Enquiry +

+ +
+
+

โœ… Do Include

+
    +
  • + โ€ข + Specific policy issues you're researching +
  • +
  • + โ€ข + Evidence-based arguments for policy change +
  • +
  • + โ€ข + Clear questions about government processes +
  • +
  • + โ€ข + Practical recommendations for policy improvement +
  • +
+
+
+

โŒ Avoid

+
    +
  • + โ€ข + Vague or general policy questions +
  • +
  • + โ€ข + Unsubstantiated claims without evidence +
  • +
  • + โ€ข + Personal opinions without research backing +
  • +
  • + โ€ข + Asking for information that's publicly available +
  • +
+
+
+
+ + +
+
+
+ ๐Ÿ“‹ +
+

Next Steps After Your Chat Session

+
+ +
+
+

+ ๐Ÿ“งCheck Your Email +

+

+ Review the detailed report sent to your email address with conversation summary and insights. +

+
+ +
+

+ ๐Ÿ“Update Your Journal +

+

+ Use the insights from your session to enhance your reflective learning journal and course participation. +

+
+ +
+

+ ๐ŸŽฏApply Your Learning +

+

+ Use the guidance received to improve your government enquiry and policy recommendation skills. +

+
+
+ +
+
+

+ ๐Ÿ’ก Remember: This chatbot helps you develop your government enquiry skills. + The insights and guidance you receive will contribute to your course participation and help you write your reflective learning journal. +

+
+
+
+ + +
+
+

+ ๐Ÿ“งNeed Additional Help? +

+

+ If you have questions about the assignment or need further guidance after using the chatbot: +

+ +
+
+ +
+ + + +
+ + + + diff --git a/gcap3056_extract/GCAP3056/intro.html b/gcap3056_extract/GCAP3056/intro.html new file mode 100644 index 00000000..a062079b --- /dev/null +++ b/gcap3056_extract/GCAP3056/intro.html @@ -0,0 +1,448 @@ + + + + + + GCAP3056 Course Hub + + + + + + + +
+ +
+

GCAP3056 Course Hub

+
+ Fall 2025 ยท Thursdays 3:30--6:20pm ยท OEM 802 +
+
+ + +
+

๐Ÿ“š Course Overview

+ +
+ +
+

Course Title

+

+ GCAP3056 -- Taking a Stand: Turning Research Insights into Policy Recommendations +

+
+ + + +
+

๐Ÿ‘จโ€๐Ÿซ Instructor

+
+
+ Dr. Simon Wang +
+
+ Language Centre + simonwang@hkbu.edu.hk +
+
+
+ + +
+

๐ŸŽฏ Course Objectives

+
+
+ โ€ข + Critically analyze government and community responses to social problems in Hong Kong. +
+
+ โ€ข + Develop actionable policy recommendations using interdisciplinary research. +
+
+ โ€ข + Engage in public advocacy through Legco reports, SCMP letters, and social media. +
+
+ โ€ข + Collaborate in teams to produce impactful research and outreach. +
+
+ โ€ข + Utilize digital tools and AI for research, writing, and reflection. +
+
+
+
+
+ + +
+

Team Projects (2025)

+

Choose from the following topics (5--6 project teams, ~5 students each):

+
    +
  • Hong Kong Observatory Chatbot
  • +
  • Emergency Alert System
  • +
  • Anti-scamming Education
  • +
  • Department of Health Port
  • +
  • Shortage of Mental Health Resources (dementia risk for elderly)
  • +
  • One team-selected topic (subject to instructor approval)
  • +
+

* Groups & topics finalized by end of Week 3.

+
+ + +
+
+ +
+ +
+ + +
+
+ +
+
+ + + + + +
+

๐Ÿ“‹ Assessment Guide

+
+ + + +
+
+
+
+

Argumentative Research Paper (25%)

+
    +
  • Group work, policy analysis & recommendations
  • +
  • Framework: background, problem, data, review, argument, recommendations, call to action
  • +
  • Draft by Week 13, final by end of semester
  • +
+
+
+ + +
+
+ + +
+
+
+

๐Ÿ“‹ Individual (35%)

+
+
+ Reflective Learning Journal + 35% +
+
+
+
+

๐Ÿ‘ฅ Group (65%)

+
+
+ Argumentative Research Paper + 25% +
+
+ Community Engagement Portfolio + 40% +
+
+
+
+
+ + +
+

+ Instructor: Dr. Simon Wang | simonwang@hkbu.edu.hk +
+ Academic honesty and responsible AI use are expected. +

+
+ + + + \ No newline at end of file diff --git a/gcap3056_extract/GCAP3056/week1-3/PracticeDiscussion.html b/gcap3056_extract/GCAP3056/week1-3/PracticeDiscussion.html new file mode 100644 index 00000000..d0d80c53 --- /dev/null +++ b/gcap3056_extract/GCAP3056/week1-3/PracticeDiscussion.html @@ -0,0 +1,841 @@ + + + + + + Practice & Discussion Activities + + + + + + + +
+ Ready! +
+ + +
+
+

Practice & Discussion

+
+ Progress: 0% + +
+
+
+
+
+
+ + +
+
+
+ โœ๏ธ +
+
+

Writing & Discussion Activities

+

+ Complete these reflective writing tasks to deepen your understanding. Each task includes support for peer discussion and AI feedback. +

+
+
+ + Write your responses +
+
+ + Discuss with peers +
+
+ + Get AI feedback +
+
+
+
+
+ + +
+
+
+
+ 1 +
+

Personal Experience Reflection

+
+
โญ•
+
+ +
+

+ Think about a time when you encountered a problem with a service or system that affected your daily life. +

+ + + + +
+
+

Detailed Instructions:

+
    +
  • โ€ข Choose a specific incident (transportation, healthcare, education, etc.)
  • +
  • โ€ข Describe what went wrong and how it affected you
  • +
  • โ€ข Write 2-3 sentences about your initial reaction
  • +
  • โ€ข Consider: Did you take any action? Why or why not?
  • +
  • โ€ข Be honest about your response - there's no "right" answer
  • +
+
+
+
+ + +
+ + +
+ Characters: 0 | Words: 0 +
+
+ + +
+ +
+

+ ๐Ÿ‘ฅ Peer Discussion +

+

+ Share your experience with classmates and learn from their perspectives. +

+ +
+ + +
+

+ ๐Ÿค– AI Feedback +

+

+ Get personalized feedback on your reflection. +

+ +
+
+ + + + +
+ + +
+
+
+
+ 2 +
+

Action Strategy Development

+
+
โญ•
+
+ +
+

+ If you could go back to the situation you described, what would be your ideal action plan to address the problem? +

+ + + +
+
+

Think about:

+
    +
  • โ€ข Who has the power to fix this problem?
  • +
  • โ€ข What would be your first step?
  • +
  • โ€ข How would you escalate if needed?
  • +
  • โ€ข What evidence would strengthen your case?
  • +
  • โ€ข How would you stay motivated through setbacks?
  • +
+
+
+
+ +
+ + +
+ Characters: 0 | Words: 0 +
+
+ +
+
+

+ ๐Ÿ‘ฅ Peer Discussion +

+

+ Compare strategies and learn from different approaches. +

+ +
+ +
+

+ ๐Ÿค– AI Feedback +

+

+ Refine your strategy with AI suggestions. +

+ +
+
+ + + +
+ + +
+
+
+
+ 3 +
+

Professional Communication

+
+
โญ•
+
+ +
+

+ Write a professional email or letter to the appropriate authority about your problem. Focus on clarity, evidence, and constructive tone. +

+ + + +
+
+

Professional Email Structure:

+
    +
  • โ€ข Subject: Clear, specific description
  • +
  • โ€ข Opening: Polite greeting and purpose
  • +
  • โ€ข Problem: Concise description with specifics
  • +
  • โ€ข Impact: How it affects you/others
  • +
  • โ€ข Request: Specific action you want
  • +
  • โ€ข Closing: Professional sign-off
  • +
+
+
+
+ +
+ + +
+ Characters: 0 | Words: 0 +
+
+ +
+
+

+ ๐Ÿ‘ฅ Peer Review +

+

+ Get feedback on tone, clarity, and effectiveness. +

+ +
+ +
+

+ ๐Ÿค– AI Review +

+

+ Improve your professional communication. +

+ +
+
+ + + +
+ + +
+
+
+
+ 4 +
+

Key Insights & Takeaways

+
+
โญ•
+
+ +
+

+ Reflect on what you've learned from this experience. What are the most important insights about citizen advocacy and policy change? +

+ + + +
+
+

Consider these questions:

+
    +
  • โ€ข What surprised you about the advocacy process?
  • +
  • โ€ข How has your confidence in taking action changed?
  • +
  • โ€ข What skills do you still need to develop?
  • +
  • โ€ข How might you apply these lessons in the future?
  • +
  • โ€ข What advice would you give to others?
  • +
+
+
+
+ +
+ + +
+ Characters: 0 | Words: 0 +
+
+ +
+
+

+ ๐Ÿ‘ฅ Peer Discussion +

+

+ Share insights and learn from diverse perspectives. +

+ +
+ +
+

+ ๐Ÿค– AI Feedback +

+

+ Deepen your reflection with AI guidance. +

+ +
+
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/gcap3056_extract/GCAP3056/week1-3/ReflectAssess.html b/gcap3056_extract/GCAP3056/week1-3/ReflectAssess.html new file mode 100644 index 00000000..425fd8b0 --- /dev/null +++ b/gcap3056_extract/GCAP3056/week1-3/ReflectAssess.html @@ -0,0 +1,93 @@ + + + + + + Report & Reflection + + + + +
+

Report & Reflection

+

Submit your report and receive feedback from your teacher via Moodle.

+ +
+ +
+

Reflection Questions

+

+ [Placeholder] This section will contain reflection questions. You can add: +

+
    +
  • โ€ข Personal insight questions
  • +
  • โ€ข Critical thinking prompts
  • +
  • โ€ข Self-evaluation checklists
  • +
  • โ€ข Journaling exercises
  • +
+
+ + +
+
+ + +
+

Report Submission

+

+ Follow these instructions to submit your report: +

+
    +
  • โ€ข Download your generated report from the main page.
  • +
  • โ€ข Log in to Moodle and navigate to the appropriate course and assignment.
  • +
  • โ€ข Upload the report file as instructed.
  • +
  • โ€ข Check Moodle for feedback from your teacher after submission.
  • +
+

+ [Placeholder] Add specific Moodle links or additional instructions if needed. +

+
+ + +
+ +
+
+
+ + + + \ No newline at end of file diff --git a/gcap3056_extract/GCAP3056/week1-3/css/styles.css b/gcap3056_extract/GCAP3056/week1-3/css/styles.css new file mode 100644 index 00000000..1cd90cb7 --- /dev/null +++ b/gcap3056_extract/GCAP3056/week1-3/css/styles.css @@ -0,0 +1,163 @@ +/* ============================================ + TEXT & COLOR ENHANCEMENTS + ============================================ */ + +/* Strong text color for main content */ +body, .text-gray-700, .text-gray-800, .text-gray-900, .text-gray-500, .text-gray-600 { + color: #222 !important; + font-weight: 400; +} + +.dark body, +.dark .text-gray-700, +.dark .text-gray-800, +.dark .text-gray-900, +.dark .text-gray-500, +.dark .text-gray-600 { + color: #f1f5f9 !important; +} + +/* Feedback and status text */ +#govMcFeedback, +#govTfFeedback, +#transFb, +#codeQuizFb, +#ltrFb, +.completion-status { + color: #222 !important; +} +.dark #govMcFeedback, +.dark #govTfFeedback, +.dark #transFb, +.dark #codeQuizFb, +.dark #ltrFb, +.dark .completion-status { + color: #f1f5f9 !important; +} + +/* MC/quiz button clarity */ +.gov-mc-btn, +.trans-btn, +.code-btn, +.quiz-option { + color: #222 !important; + font-weight: 500; +} +.dark .gov-mc-btn, +.dark .trans-btn, +.dark .code-btn, +.dark .quiz-option { + color: #f1f5f9 !important; + font-weight: 500; +} + +/* ============================================ + BULLET POINT ALIGNMENT + ============================================ */ + +ul, ul.list-disc, ul.list-decimal { + margin-left: 0 !important; + padding-left: 1em !important; + list-style-position: outside !important; +} +li { + margin-left: 0 !important; + padding-left: 0 !important; + margin-bottom: 0.5em !important; + color: inherit !important; +} +.dark ul li { + color: #f1f5f9 !important; +} + +/* No faded bullets anywhere */ +ul li, ul.list-disc li, ul.list-decimal li { + color: inherit !important; +} + +/* ============================================ + PAGE WIDTH & LAYOUT + ============================================ */ + +/* Max width for main containers */ +.max-w-4xl, .max-w-6xl { + max-width: 1400px !important; + width: 98% !important; + margin-left: auto !important; + margin-right: auto !important; +} + +@media (min-width: 1600px) { + .max-w-4xl, .max-w-6xl { + max-width: 1600px !important; + width: 95% !important; + } +} + +/* Adjust for mobile/tablet */ +@media (max-width: 1023px) { + .max-w-4xl, .max-w-6xl { + width: 100% !important; + padding-left: 0.5rem !important; + padding-right: 0.5rem !important; + } +} + +/* Content area min-height for better visual balance */ +#contentArea { + min-height: 60vh; + transition: all 0.3s ease; + background: inherit; +} + +/* ============================================ + NOTIFICATION & FEEDBACK + ============================================ */ + +.notification { + background: #fff; + color: #222; + border-radius: 8px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15); + z-index: 1000; +} +.dark .notification { + background: #222; + color: #f1f5f9; +} + +/* ============================================ + PRINT & ACCESSIBILITY + ============================================ */ +@media print { + .notification, #loadingIndicator, button { display: none !important; } + .max-w-4xl, .max-w-6xl { max-width: none !important; width: 100% !important; } +} + +html { scroll-behavior: smooth; } + +/* Accessibility: Focus ring for all buttons */ +button:focus, .section-btn:focus { + outline: 2px solid #5D5CDE; + outline-offset: 2px; +} + +/* ============================================ + MISCELLANEOUS + ============================================ */ + +/* Remove unwanted faded text for all gray variants */ +.text-gray-500, .text-gray-600, .text-gray-700 { + color: #222 !important; +} +.dark .text-gray-500, .dark .text-gray-600, .dark .text-gray-700 { + color: #f1f5f9 !important; +} + +/* Ensure all list items are visible in dark mode */ +ul li { + color: inherit !important; +} +.dark ul li { + color: #f1f5f9 !important; +} \ No newline at end of file diff --git a/gcap3056_extract/GCAP3056/week1-3/index.html b/gcap3056_extract/GCAP3056/week1-3/index.html new file mode 100644 index 00000000..d089863a --- /dev/null +++ b/gcap3056_extract/GCAP3056/week1-3/index.html @@ -0,0 +1,156 @@ + + + + + + GCAP3056 Weeks 1โ€“3: Orientation & Issue Discovery + + + + + +
+ + +
+

GCAP3056 Weeks 1โ€“3

+
Orientation & Issue Discovery
+
+ + 1 hour interactive lecture + group tasks & meetings + +
+ + +
+
+
+
+
Overall Progress: 0%
+
+
+ + +
+
+
1
+
+

Module 1: Orientation & Issue Discovery

+
Open Now
+
+
+
+
    +
  • Discover the basics of public policy and advocacy in Hong Kong
  • +
  • Draft your first โ€œpolicy askโ€ and learn to write for public impact
  • +
  • Meet your team and start brainstorming your capstone project
  • +
+
+
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + +
+
+
๐ŸŽ“
+

Welcome to Module 1!

+

Select an activity above (lecture, practice, or reflection) to begin your learning journey.

+
+ Progress will be tracked as you complete each section. +
+
+
+ + +
+ + + +
+ + +
+ Last updated: +
+
+ + +
+ + + + + diff --git a/gcap3056_extract/GCAP3056/week1-3/interactiveLecture.html b/gcap3056_extract/GCAP3056/week1-3/interactiveLecture.html new file mode 100644 index 00000000..3013859b --- /dev/null +++ b/gcap3056_extract/GCAP3056/week1-3/interactiveLecture.html @@ -0,0 +1,353 @@ + + + + + Interactive Lecture: Accountability, Transparency & Access to Information + + + + + + +
+ +

๐Ÿ—ž๏ธ Accountability, Transparency, and the Power of Letters

+

+ In this module, explore why government must be kept in check, how information access supports accountability, and how ordinary people (including you!) can join public debate via Letters to the Editor. +

+ + +
+

1. Government is a "necessary evil"

+

+ The phrase โ€œGovernment is a necessary evilโ€ sounds like a paradox. Why do people say this? +

+ +
+
Which of the following best expresses the idea?
+
+ + + +
+
+
+ +
+
True or False: In a modern society, we can do without any government at all.
+
+ + +
+
+
+
+ + +
+

2. Accountability and Transparency

+

+ Accountability means government staff should answer for their actions and face consequences if they misuse public resources. Why? Because they are funded by taxpayers' money. +

+
+
Fill in the blank: Government staff should be held accountable because __________.
+ + + +
+

+ Transparency means government actions should be open to the public, so staff can be held accountable. For this to work, we need a free press where people can voice their views. +

+
+
Which of the following helps most with transparency?
+
+ + +
+
+
+
+ + +
+

3. Code on Access to Information

+

+ The Hong Kong Government's Code on Access to Information is a policy that lets any member of the public request information from government departments. +

+
    +
  • The government must inform the public about its services and policy decisions.
  • +
  • Information can be provided routinely or upon request, unless there are specific reasons to refuse (like privacy, security, or legal restrictions).
  • +
  • Requests should be handled promptly and helpfully. If unclear, the department will contact you for clarification.
  • +
  • There are procedures for review or complaint if you believe the Code was not properly applied.
  • +
  • The Code is available online: access.gov.hk
  • +
+
+
Quick Quiz: What can you do if your information request is refused?
+
+ + + +
+
+
+
+ Note: The Code defines what info can be released, and sets a time frame for reply (usually 10 days, up to 21 days for complex cases). If refused, you can ask for a review or complain to the Ombudsman.
+ Learn more here. +
+
+ + +
+

4. Video: Access to Information (Critical Review)

+
+ +
+
+ Critical Review: The official video explains the basics of requesting information from the Hong Kong government, but it misses several vital points: +
+
    +
  • It does not mention the official time frame for government replies (normally within 10 days, extendable to 21 days).
  • +
  • It omits the right to request a review or complain to the Ombudsman if your request is refused or delayed.
  • +
  • It gives examples, but does not explain what types of information may be refused (privacy, security, etc.).
  • +
+
+ + + + +
+
+ + +
+

5. Letters to the Editor: Your Voice Matters

+

+ Letters to the Editor, like those in the South China Morning Post, bridge the gap between the public and the โ€œivory tower.โ€ Anyone can write, share opinions, and influence public debateโ€”even students! +

+
+
Why are Letters to the Editor important? (Select all that apply)
+
+ + + +
+ + +
+
+ + +
+ + +
+ + +
+ +
+
+ + + + \ No newline at end of file diff --git a/gcap3056_extract/GCAP3056/week1-3/js/course-app.js b/gcap3056_extract/GCAP3056/week1-3/js/course-app.js new file mode 100644 index 00000000..2238dfee --- /dev/null +++ b/gcap3056_extract/GCAP3056/week1-3/js/course-app.js @@ -0,0 +1,2101 @@ +// Course Container Application - Combined JavaScript +// All classes in one file to avoid loading conflicts + +// Notification Manager +class NotificationManager { + constructor() { + this.container = this.createContainer(); + this.notifications = []; + } + + createContainer() { + let container = document.getElementById('notificationContainer'); + if (!container) { + container = document.createElement('div'); + container.id = 'notificationContainer'; + container.className = 'fixed top-4 right-4 z-50 space-y-2'; + document.body.appendChild(container); + } + return container; + } + + show(message, type = 'info', duration = 5000) { + const notification = this.createNotification(message, type); + this.container.appendChild(notification); + + // Animate in + setTimeout(() => notification.classList.add('show'), 100); + + // Auto remove + setTimeout(() => this.remove(notification), duration); + + return notification; + } + + createNotification(message, type) { + const notification = document.createElement('div'); + notification.className = `notification bg-white dark:bg-gray-800 border-l-4 p-4 rounded-lg shadow-lg transform translate-x-full transition-all duration-300 ${this.getTypeClasses(type)}`; + + notification.innerHTML = ` +
+
+ ${this.getIcon(type)} +
+
+

${message}

+
+ +
+ `; + + return notification; + } + + getTypeClasses(type) { + const classes = { + success: 'border-green-500', + error: 'border-red-500', + warning: 'border-yellow-500', + info: 'border-blue-500' + }; + return classes[type] || classes.info; + } + + getIcon(type) { + const icons = { + success: 'โœ…', + error: 'โŒ', + warning: 'โš ๏ธ', + info: 'โ„น๏ธ' + }; + return icons[type] || icons.info; + } + + remove(notification) { + notification.classList.remove('show'); + setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + }, 300); + } +} + +// Timer Manager +class TimerManager { + constructor() { + this.timers = new Map(); + this.startTime = Date.now(); + } + + startTimer(sectionId) { + if (!this.timers.has(sectionId)) { + this.timers.set(sectionId, { + startTime: Date.now(), + totalTime: 0, + isRunning: true + }); + } else { + const timer = this.timers.get(sectionId); + if (!timer.isRunning) { + timer.startTime = Date.now(); + timer.isRunning = true; + } + } + console.log(`โฑ๏ธ Timer started for section: ${sectionId}`); + } + + stopTimer(sectionId) { + const timer = this.timers.get(sectionId); + if (timer && timer.isRunning) { + timer.totalTime += Date.now() - timer.startTime; + timer.isRunning = false; + console.log(`โน๏ธ Timer stopped for section: ${sectionId}`); + } + } + + getTimeSpent(sectionId) { + const timer = this.timers.get(sectionId); + if (!timer) return 0; + + let totalTime = timer.totalTime; + if (timer.isRunning) { + totalTime += Date.now() - timer.startTime; + } + return totalTime; + } + + getTotalTimeSpent() { + return Date.now() - this.startTime; + } +} + +// Progress Manager +class ProgressManager { + constructor() { + this.progressData = new Map(); + } + + markSectionProgress(sectionId, progress) { + this.progressData.set(sectionId, Math.max(0, Math.min(100, progress))); + + // Update state manager + if (window.stateManager) { + window.stateManager.updateSection(sectionId, { + progress: progress, + completed: progress >= 100, + timeSpent: window.timerManager ? window.timerManager.getTimeSpent(sectionId) : 0 + }); + } + + // Update UI + if (window.app) { + window.app.updateSectionProgress(sectionId, progress); + } + + console.log(`๐Ÿ“ˆ Progress updated for ${sectionId}: ${progress}%`); + } + + getSectionProgress(sectionId) { + return this.progressData.get(sectionId) || 0; + } + + getOverallProgress() { + if (this.progressData.size === 0) return 0; + + const total = Array.from(this.progressData.values()).reduce((sum, progress) => sum + progress, 0); + return total / this.progressData.size; + } +} + +// State Management System +class StateManager { + constructor() { + this.state = { + user: { + id: null, + name: 'Student', + startTime: Date.now(), + lastActivity: Date.now() + }, + course: { + id: 'course-001', + name: 'Interactive Learning Course', + sections: { + lecture: { + progress: 0, + timeSpent: 0, + completed: false, + lastAccessed: null, + data: {} + }, + practice: { + progress: 0, + timeSpent: 0, + completed: false, + lastAccessed: null, + data: {} + }, + reflect: { + progress: 0, + timeSpent: 0, + completed: false, + lastAccessed: null, + data: {} + } + } + }, + settings: { + theme: 'light', + autoSave: true, + notifications: true + }, + interactions: [], + reports: [] + }; + + this.loadState(); + this.setupAutoSave(); + } + + loadState() { + try { + const savedState = localStorage.getItem('courseState'); + if (savedState) { + const parsed = JSON.parse(savedState); + this.state = { ...this.state, ...parsed }; + console.log('โœ… State loaded from localStorage'); + } + } catch (error) { + console.warn('โš ๏ธ Failed to load state:', error); + } + } + + saveState() { + try { + this.state.user.lastActivity = Date.now(); + localStorage.setItem('courseState', JSON.stringify(this.state)); + console.log('โœ… State saved to localStorage'); + return true; + } catch (error) { + console.error('โŒ Failed to save state:', error); + return false; + } + } + + setupAutoSave() { + if (this.state.settings.autoSave) { + setInterval(() => { + this.saveState(); + }, 30000); // Auto-save every 30 seconds + } + } + + updateSection(sectionId, updates) { + if (this.state.course.sections[sectionId]) { + this.state.course.sections[sectionId] = { + ...this.state.course.sections[sectionId], + ...updates, + lastAccessed: Date.now() + }; + + if (this.state.settings.autoSave) { + this.saveState(); + } + + console.log(`๐Ÿ“Š Section ${sectionId} updated:`, updates); + return true; + } + return false; + } + + getSectionProgress(sectionId) { + return this.state.course.sections[sectionId]?.progress || 0; + } + + getSectionData(sectionId) { + return this.state.course.sections[sectionId] || null; + } + + addInteraction(interaction) { + this.state.interactions.push({ + ...interaction, + timestamp: Date.now(), + id: `interaction-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + }); + } + + getOverallProgress() { + const sections = Object.values(this.state.course.sections); + const totalProgress = sections.reduce((sum, section) => sum + section.progress, 0); + return sections.length > 0 ? totalProgress / sections.length : 0; + } + + getTimeSpent() { + return Date.now() - this.state.user.startTime; + } + + getFullState() { + return { + ...this.state, + computed: { + overallProgress: this.getOverallProgress(), + totalTimeSpent: this.getTimeSpent(), + sectionsCompleted: Object.values(this.state.course.sections).filter(s => s.completed).length, + lastActivity: new Date(this.state.user.lastActivity).toLocaleString() + } + }; + } + + resetState() { + localStorage.removeItem('courseState'); + location.reload(); + } + + exportState() { + return JSON.stringify(this.state, null, 2); + } + + importState(stateJson) { + try { + const newState = JSON.parse(stateJson); + this.state = newState; + this.saveState(); + return true; + } catch (error) { + console.error('Failed to import state:', error); + return false; + } + } +} + +// Enhanced Content Loader Module +class ContentLoader { + constructor() { + this.cache = new Map(); + this.loadingQueue = new Set(); + } + + async loadContent(url) { + if (this.loadingQueue.has(url)) { + return; // Already loading + } + + this.loadingQueue.add(url); + this.toggleLoading(true); + + const section = this.getSectionFromUrl(url); + + try { + let content; + + // Check cache first + if (this.cache.has(url)) { + content = this.cache.get(url); + console.log(`๐Ÿ“„ Loaded ${section} from cache`); + } else { + // Try to fetch the actual file + console.log(`๐ŸŒ Fetching ${url}...`); + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + content = await response.text(); + this.cache.set(url, content); + console.log(`โœ… Fetched and cached ${section} content`); + } + + // Clear the content area + const contentArea = document.getElementById('contentArea'); + contentArea.innerHTML = ''; + + // Create a temporary container to parse the HTML + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = content; + + // Extract and move body content (skip head) + const bodyContent = tempDiv.querySelector('body'); + if (bodyContent) { + // Move all body content to content area + while (bodyContent.firstChild) { + contentArea.appendChild(bodyContent.firstChild); + } + } else { + // If no body tag, just use all content + while (tempDiv.firstChild) { + contentArea.appendChild(tempDiv.firstChild); + } + } + + // Execute any inline scripts + this.executeScripts(contentArea); + + // Override any report functions in the loaded content + setTimeout(() => { + overrideContentReportFunctions(); + console.log('๐Ÿ”ง Report functions overridden after content load'); + }, 1000); + + // Set up data collection for interactive content + setTimeout(() => { + setupInteractiveDataCollection(section); + console.log('๐Ÿ“Š Data collection setup for:', section); + }, 1500); + // Update state + if (window.stateManager) { + window.stateManager.updateSection(section, { + lastAccessed: new Date().toISOString() + }); + } + + // Start timer + if (window.timerManager) { + window.timerManager.startTimer(section); + } + + // Show success notification + if (window.notificationManager) { + window.notificationManager.show(`Loaded ${section} content successfully!`, 'success'); + } + + console.log(`๐ŸŽ‰ Successfully loaded ${section} content`); + + } catch (error) { + console.error('โŒ Error loading content:', error); + + // Provide fallback content + const fallbackContent = this.createFallbackContent(section); + document.getElementById('contentArea').innerHTML = fallbackContent; + + if (window.notificationManager) { + window.notificationManager.show(`Loaded demo content for ${section}`, 'warning'); + } + } finally { + this.loadingQueue.delete(url); + this.toggleLoading(false); + } + } + + executeScripts(container) { + // Find all script tags in the loaded content + const scripts = container.querySelectorAll('script'); + + scripts.forEach(oldScript => { + // Create a new script element + const newScript = document.createElement('script'); + + // Copy attributes + Array.from(oldScript.attributes).forEach(attr => { + newScript.setAttribute(attr.name, attr.value); + }); + + // Copy script content + newScript.appendChild(document.createTextNode(oldScript.innerHTML)); + + // Replace old script with new one to trigger execution + oldScript.parentNode.replaceChild(newScript, oldScript); + }); + + console.log(`โšก Executed ${scripts.length} scripts for loaded content`); + } + + getSectionFromUrl(url) { + const filename = url.split('/').pop().split('.')[0]; + if (filename.toLowerCase().includes('lecture')) return 'lecture'; + if (filename.toLowerCase().includes('practice')) return 'practice'; + if (filename.toLowerCase().includes('reflect')) return 'reflect'; + return 'lecture'; + } + + createFallbackContent(section) { + const contents = { + lecture: ` +
+

๐Ÿ“š Interactive Lecture

+
+

Demo Content

+

This is placeholder content for the interactive lecture section. In your actual implementation, this would contain:

+
    +
  • Video lectures with interactive elements
  • +
  • Embedded quizzes and knowledge checks
  • +
  • Downloadable resources and materials
  • +
  • Progress tracking and bookmarking
  • +
+ +
+
+ `, + practice: ` +
+

๐Ÿ’ฌ Practice & Discussion

+
+

Demo Content

+

This is placeholder content for the practice and discussion section. Features would include:

+
    +
  • Interactive exercises and simulations
  • +
  • Discussion forums and peer collaboration
  • +
  • Case studies and real-world applications
  • +
  • Peer review and feedback systems
  • +
+ +
+
+ `, + reflect: ` +
+

๐ŸŽฏ Reflect & Assess

+
+

Demo Content

+

This is placeholder content for the reflection and assessment section. Components include:

+
    +
  • Self-reflection exercises and journaling
  • +
  • Formative and summative assessments
  • +
  • Portfolio creation and showcasing
  • +
  • Goal setting and progress evaluation
  • +
+ +
+
+ ` + }; + + return contents[section] || contents.lecture; + } + + toggleLoading(show) { + const loader = document.getElementById('loadingIndicator'); + if (loader) { + loader.style.transform = show ? 'scaleX(1)' : 'scaleX(0)'; + } + } + + clearCache() { + this.cache.clear(); + console.log('๐Ÿ—‘๏ธ Content cache cleared'); + } + + preloadContent(urls) { + console.log('๐Ÿš€ Preloading content...'); + urls.forEach(async (url) => { + try { + const response = await fetch(url); + if (response.ok) { + const content = await response.text(); + this.cache.set(url, content); + console.log(`โœ… Preloaded: ${url}`); + } + } catch (error) { + console.warn(`โš ๏ธ Failed to preload ${url}:`, error); + } + }); + } +} + +// Report Generator +class ReportGenerator { + constructor() { + this.reports = []; + } + + generateDetailedReport(data) { + console.log('๐Ÿ“Š Generating detailed report...'); + + const report = this.createReportData(data); + this.showReportModal(report, 'detailed'); + } + + showInteractiveReport(lectureData) { + console.log('๐Ÿ“‹ Showing interactive content report...'); + + const report = this.createInteractiveReportData(lectureData); + this.showReportModal(report, 'interactive'); + } + + createReportData(stateData) { + const sections = stateData.course?.sections || {}; + const overallProgress = stateData.computed?.overallProgress || 0; + const totalTime = stateData.computed?.totalTimeSpent || 0; + + return { + metadata: { + generated: new Date().toISOString(), + reportType: 'Course Progress Report' + }, + summary: { + overallProgress: Math.round(overallProgress), + totalTimeSpent: this.formatDuration(totalTime), + sectionsCompleted: Object.values(sections).filter(s => s.completed).length, + totalSections: Object.keys(sections).length + }, + sections: Object.entries(sections).map(([id, data]) => ({ + id, + name: this.getSectionName(id), + progress: data.progress || 0, + timeSpent: this.formatDuration(data.timeSpent || 0), + completed: data.completed || false + })), + interactions: stateData.interactions || [] + }; + } + + createInteractiveReportData(lectureData) { + const duration = lectureData.timeSpent || (Date.now() - lectureData.startTime); + + return { + metadata: { + generated: new Date().toISOString(), + contentType: 'Interactive Lecture' + }, + performance: { + score: lectureData.score, + choiceSelected: lectureData.choiceSelected, + timeSpent: this.formatDuration(duration), + completed: lectureData.completed || false + }, + sectionProgress: lectureData.sectionProgress || {}, + interactions: lectureData.interactions || [] + }; + } + + showReportModal(reportData, template = 'detailed') { + let modal = document.getElementById('reportModal'); + if (!modal) { + modal = this.createReportModal(); + } + + const content = document.getElementById('reportModalContent'); + content.innerHTML = this.generateReportHTML(reportData, template); + + modal.style.display = 'flex'; + + this.setupModalEventListeners(modal, reportData); + } + + createReportModal() { + const modal = document.createElement('div'); + modal.id = 'reportModal'; + modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4'; + modal.style.display = 'none'; + + modal.innerHTML = ` +
+
+

+ ๐Ÿ“Š + Learning Report +

+ +
+
+ +
+
+
+ + +
+
+
+ `; + + document.body.appendChild(modal); + + // Add bottom close button handler + const bottomCloseBtn = modal.querySelector('#closeReportModalBottom'); + if (bottomCloseBtn) { + bottomCloseBtn.onclick = (e) => { + e.preventDefault(); + modal.style.display = 'none'; + }; + } + + return modal; +} + + generateReportHTML(data, template) { + if (template === 'interactive') { + return ` +
+
+
+
${data.performance.score || 'N/A'}%
+
Score
+
+
+
${data.performance.timeSpent}
+
Time
+
+
+
${data.performance.completed ? 'โœ…' : 'โณ'}
+
Status
+
+
+
${data.interactions.length}
+
Interactions
+
+
+
+

Choice Selected: ${data.performance.choiceSelected || 'None'}

+

Section Progress:

+ ${Object.entries(data.sectionProgress).map(([section, progress]) => ` +
+ ${this.getSectionName(section)} + ${progress}% +
+ `).join('')} +
+
+ `; + } else { + return ` +
+
+
+
${data.summary.overallProgress}%
+
Progress
+
+
+
${data.summary.totalTimeSpent}
+
Time
+
+
+
${data.summary.sectionsCompleted}
+
Completed
+
+
+
${data.interactions.length}
+
Interactions
+
+
+
+

Section Details:

+ ${data.sections.map(section => ` +
+ ${section.name} + ${section.progress}% (${section.timeSpent}) +
+ `).join('')} +
+
+ `; + } + } + + setupModalEventListeners(modal, reportData) { + // Multiple ways to close the modal + const closeButtons = modal.querySelectorAll('#closeReportModal, #closeReportModalBottom'); + closeButtons.forEach(btn => { + if (btn) { + btn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + modal.style.display = 'none'; + console.log('โœ… Report modal closed'); + }); + } + }); + + // Download functionality + const downloadBtn = modal.querySelector('#downloadReportBtn'); + if (downloadBtn) { + downloadBtn.addEventListener('click', (e) => { + e.preventDefault(); + const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `report-${new Date().toISOString().split('T')[0]}.json`; + a.click(); + URL.revokeObjectURL(url); + }); + } + + // Click outside to close + modal.addEventListener('click', (e) => { + if (e.target === modal) { + modal.style.display = 'none'; + } + }); + + // Escape key to close + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && modal.style.display === 'flex') { + modal.style.display = 'none'; + } + }); +} + + formatDuration(milliseconds) { + const minutes = Math.floor(milliseconds / 60000); + const seconds = Math.floor((milliseconds % 60000) / 1000); + return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`; + } + + getSectionName(sectionId) { + const names = { + '1': 'Opening Assessment', + '2': 'Content Review', + '3': 'Completion', + 'lecture': 'Interactive Lecture', + 'practice': 'Practice & Discussion', + 'reflect': 'Reflect & Assess' + }; + return names[sectionId] || sectionId; + } +} + +// Main Application Controller +class App { + constructor() { + this.initialized = false; + this.currentContent = null; + this.startTime = Date.now(); + } + + async init() { + if (this.initialized) return; + + try { + console.log('๐Ÿš€ Initializing Course Container App...'); + + // Initialize core managers + this.initializeManagers(); + + // Initialize course data + this.initializeCourseData(); + + // Initialize theme + this.initializeTheme(); + + this.initialized = true; + console.log('โœ… App initialization complete'); + + // Show welcome notification + if (window.notificationManager) { + window.notificationManager.show('Course container loaded successfully!', 'success'); + } + + } catch (error) { + console.error('โŒ App initialization failed:', error); + if (window.notificationManager) { + window.notificationManager.show('Failed to initialize course container', 'error'); + } + } + } + + initializeManagers() { + console.log('๐Ÿ“ฆ Initializing managers...'); + + // Initialize managers directly since they're all in this file + window.notificationManager = new NotificationManager(); + window.stateManager = new StateManager(); + window.timerManager = new TimerManager(); + window.progressManager = new ProgressManager(); + window.reportGenerator = new ReportGenerator(); + window.contentLoader = new ContentLoader(); + + console.log('โœ… All managers initialized successfully'); + } + + initializeCourseData() { + // Set current date + const lastUpdatedElement = document.getElementById('lastUpdated'); + if (lastUpdatedElement) { + lastUpdatedElement.textContent = new Date().toLocaleDateString(); + } + + // Initialize section progress indicators + this.updateSectionIndicators(); + } + + initializeTheme() { + // Check for saved theme preference or default to 'light' + const theme = localStorage.getItem('theme') || 'light'; + this.setTheme(theme); + } + + setTheme(theme) { + if (theme === 'dark') { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + localStorage.setItem('theme', theme); + } + + toggleDarkMode() { + const isDark = document.documentElement.classList.contains('dark'); + this.setTheme(isDark ? 'light' : 'dark'); + if (window.notificationManager) { + window.notificationManager.show(`Switched to ${isDark ? 'light' : 'dark'} mode`, 'info'); + } + } + + updateSectionIndicators() { + const sections = ['lecture', 'practice', 'reflect']; + sections.forEach(section => { + const progress = window.stateManager ? window.stateManager.getSectionProgress(section) : 0; + this.updateSectionProgress(section, progress); + }); + } + + updateSectionProgress(section, progress) { + const sectionButton = document.querySelector(`[data-section="${section}"]`); + if (sectionButton) { + const progressBar = sectionButton.querySelector('.section-progress'); + const statusSpan = sectionButton.querySelector('.completion-status'); + + if (progressBar) { + progressBar.style.width = progress + '%'; + } + + if (statusSpan) { + if (progress >= 100) { + statusSpan.textContent = 'โœ… Completed'; + statusSpan.className = 'completion-status text-green-600 dark:text-green-400'; + } else if (progress > 0) { + statusSpan.textContent = `โณ In Progress (${progress}%)`; + statusSpan.className = 'completion-status text-yellow-600 dark:text-yellow-400'; + } else { + statusSpan.textContent = 'โญ• Not Started'; + statusSpan.className = 'completion-status text-gray-500 dark:text-gray-400'; + } + } + } + + this.updateOverallProgress(); + } + + updateOverallProgress() { + const sections = document.querySelectorAll('.section-progress'); + let totalProgress = 0; + + sections.forEach(bar => { + const width = parseFloat(bar.style.width) || 0; + totalProgress += width; + }); + + const overallProgress = sections.length > 0 ? totalProgress / sections.length : 0; + const overallBar = document.getElementById('overallProgressBar'); + const overallText = document.getElementById('overallProgressText'); + + if (overallBar) { + overallBar.style.width = overallProgress + '%'; + } + + if (overallText) { + overallText.textContent = Math.round(overallProgress) + '%'; + } + } + + async loadContent(url) { + if (window.contentLoader) { + await window.contentLoader.loadContent(url); + } + } + + saveProgress() { + try { + if (window.stateManager) { + window.stateManager.saveState(); + if (window.notificationManager) { + window.notificationManager.show('Progress saved successfully!', 'success'); + } + } + } catch (error) { + console.error('Save failed:', error); + if (window.notificationManager) { + window.notificationManager.show('Failed to save progress', 'error'); + } + } + } + + generateReport() { + try { + if (window.stateManager && window.reportGenerator) { + const reportData = window.stateManager.getFullState(); + window.reportGenerator.generateDetailedReport(reportData); + } + } catch (error) { + console.error('Report generation failed:', error); + if (window.notificationManager) { + window.notificationManager.show('Failed to generate report', 'error'); + } + } + } + + goBack() { + if (window.history.length > 1) { + window.history.back(); + } else { + if (window.notificationManager) { + window.notificationManager.show('No previous page to return to', 'info'); + } + } + } +} + +// Global functions +window.updateSectionProgress = function(section, progress) { + console.log(`๐Ÿ“ˆ Section ${section} progress updated to ${progress}%`); + + if (window.stateManager) { + window.stateManager.updateSection(section, { progress: progress }); + } + + if (window.app) { + window.app.updateSectionProgress(section, progress); + } +}; + +window.loadContent = function(url) { + if (window.app) { + window.app.loadContent(url); + } +}; + +window.toggleDarkMode = function() { + if (window.app) { + window.app.toggleDarkMode(); + } +}; + +window.goBack = function() { + if (window.app) { + window.app.goBack(); + } +}; + +window.saveProgress = function() { + if (window.app) { + window.app.saveProgress(); + } +}; + +window.generateReport = function(lectureData = null) { + if (lectureData) { + // Called from interactive content + if (window.reportGenerator) { + window.reportGenerator.showInteractiveReport(lectureData); + } + } else { + // Called from main UI + if (window.app) { + window.app.generateReport(); + } + } +}; + +// Initialize app when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + window.app = new App(); + window.app.init(); +}); + +// Handle messages from iframe content +window.addEventListener('message', (event) => { + if (event.data.type === 'generateReport') { + window.generateReport(event.data.data); + } +}); +// Global function to generate report (called by HTML button) +function generateReport() { + console.log('๐Ÿ” Generate Report button clicked from HTML'); + + if (window.app && window.app.generateReport) { + window.app.generateReport(); + } else if (window.reportGenerator) { + window.reportGenerator.generateReport(); + } else { + // Create a simple report if no app exists + console.log('๐Ÿ“Š Creating simple report...'); + const modal = createSimpleReportModal(); + modal.style.display = 'flex'; + } +} + +// Global function to save progress (called by HTML button) +function saveProgress() { + console.log('๐Ÿ’พ Save Progress button clicked from HTML'); + + if (window.app && window.app.saveProgress) { + window.app.saveProgress(); + } else { + // Simple save functionality + try { + const progressData = { + timestamp: new Date().toISOString(), + message: 'Progress saved successfully!' + }; + localStorage.setItem('courseProgress', JSON.stringify(progressData)); + + // Show notification + if (window.notificationManager) { + window.notificationManager.show('Progress saved!', 'success'); + } else { + alert('Progress saved successfully!'); + } + } catch (error) { + console.error('โŒ Error saving progress:', error); + alert('Error saving progress'); + } + } +} +// Function to collect user data from interactions and localStorage +function collectUserData() { + console.log('๐Ÿ“Š Collecting user data...'); + + // Get data from localStorage + const savedData = localStorage.getItem('courseInteractionData'); + const interactionData = savedData ? JSON.parse(savedData) : {}; + + // Get current form data + const currentInputs = document.querySelectorAll('input, textarea, select, [contenteditable="true"]'); + const currentData = []; + + currentInputs.forEach((input, index) => { + const value = input.value || input.textContent; + if (value && value.trim()) { + currentData.push({ + type: input.tagName.toLowerCase(), + id: input.id || `element-${index}`, + content: value, + timestamp: new Date().toISOString() + }); + } + }); + + // Calculate statistics + const lectureData = interactionData.lecture || []; + const practiceData = interactionData.practice || []; + const discussionData = interactionData.discussion || []; + + const totalInteractions = lectureData.length + practiceData.length + discussionData.length + currentData.length; + const lectureAnswers = lectureData.filter(item => item.type === 'multiple_choice').length; + const practiceInputs = practiceData.length; + const discussionInputs = discussionData.length; + + // Calculate simple progress based on interactions + const lectureProgress = lectureAnswers > 0 ? Math.min(100, lectureAnswers * 25) : 0; + const practiceProgress = practiceInputs > 0 ? Math.min(100, practiceInputs * 20) : 0; + const discussionProgress = discussionInputs > 0 ? Math.min(100, discussionInputs * 20) : 0; + const overallProgress = Math.round((lectureProgress + practiceProgress + discussionProgress) / 3); + + return { + timestamp: new Date().toISOString(), + sections: { + lecture: lectureData, + practice: practiceData, + discussion: discussionData, + current: currentData + }, + summary: { + totalInteractions: totalInteractions, + lectureAnswers: lectureAnswers, + practiceInputs: practiceInputs, + discussionInputs: discussionInputs, + overallProgress: overallProgress, + lectureProgress: lectureProgress, + practiceProgress: practiceProgress, + discussionProgress: discussionProgress + } + }; +} + +// Function to populate dynamic report content +function populateReportContent(userData) { + console.log('๐Ÿ“‹ Populating report with real data:', userData); + + // Update statistics in header + const statsContainer = document.getElementById('reportStats'); + if (statsContainer) { + statsContainer.innerHTML = ` +
+
${userData.summary.overallProgress}%
+
Progress
+
+
+
${Math.round((Date.now() - (JSON.parse(localStorage.getItem('courseStartTime') || Date.now()))) / 60000)}m
+
Time Spent
+
+
+
${[userData.summary.lectureProgress, userData.summary.practiceProgress, userData.summary.discussionProgress].filter(p => p > 0).length}/3
+
Sections Done
+
+
+
${userData.summary.totalInteractions}
+
Interactions
+
+ `; + } + + // Update section details + const updateSectionCard = (sectionName, progress, interactions, status) => { + const sectionCard = document.querySelector(`[data-section="${sectionName}"]`); + if (sectionCard) { + const progressBar = sectionCard.querySelector('.bg-blue-500, .bg-green-500, .bg-purple-500'); + const statusText = sectionCard.querySelector('.font-bold'); + const progressText = sectionCard.querySelector('.text-blue-600, .text-green-600, .text-purple-600'); + + if (progressBar) progressBar.style.width = progress + '%'; + if (progressText) progressText.textContent = progress + '%'; + if (statusText) statusText.textContent = status; + } + }; + + // Update each section + updateSectionCard('lecture', + userData.summary.lectureProgress, + userData.summary.lectureAnswers, + userData.summary.lectureProgress >= 100 ? 'โœ… Completed' : + userData.summary.lectureProgress > 0 ? 'โณ In Progress' : 'โญ• Ready to start' + ); + + updateSectionCard('practice', + userData.summary.practiceProgress, + userData.summary.practiceInputs, + userData.summary.practiceProgress >= 100 ? 'โœ… Completed' : + userData.summary.practiceProgress > 0 ? 'โณ In Progress' : 'โญ• Ready to start' + ); + + updateSectionCard('discussion', + userData.summary.discussionProgress, + userData.summary.discussionInputs, + userData.summary.discussionProgress >= 100 ? 'โœ… Completed' : + userData.summary.discussionProgress > 0 ? 'โณ In Progress' : 'โญ• Ready to start' + ); + + // Update markdown content + updateMarkdownContent(userData); +} + +// Function to update markdown content with real data +function updateMarkdownContent(userData) { + const markdownContent = document.getElementById('markdownContent'); + if (markdownContent) { + const lectureAnswers = userData.sections.lecture.filter(item => item.type === 'multiple_choice'); + const practiceTexts = userData.sections.practice; + const discussionTexts = userData.sections.discussion; + + let interactionsSection = '## Learning Interactions\n\n'; + + if (lectureAnswers.length > 0) { + interactionsSection += '### ๐Ÿ“š Interactive Lecture Responses\n'; + lectureAnswers.forEach((answer, index) => { + interactionsSection += `${index + 1}. **${answer.question}**\n - Answer: ${answer.answer}\n - Time: ${new Date(answer.timestamp).toLocaleString()}\n\n`; + }); + } + + if (practiceTexts.length > 0) { + interactionsSection += '### ๐Ÿ‹๏ธ Practice Section Inputs\n'; + practiceTexts.forEach((input, index) => { + interactionsSection += `${index + 1}. **Input ${input.inputId}**\n - Content: ${input.content.substring(0, 100)}${input.content.length > 100 ? '...' : ''}\n - Time: ${new Date(input.timestamp).toLocaleString()}\n\n`; + }); + } + + if (discussionTexts.length > 0) { + interactionsSection += '### ๐Ÿ’ฌ Discussion Section Inputs\n'; + discussionTexts.forEach((input, index) => { + interactionsSection += `${index + 1}. **Input ${input.inputId}**\n - Content: ${input.content.substring(0, 100)}${input.content.length > 100 ? '...' : ''}\n - Time: ${new Date(input.timestamp).toLocaleString()}\n\n`; + }); + } + + if (userData.summary.totalInteractions === 0) { + interactionsSection += 'No interactions recorded yet. Complete some activities to see detailed results here.\n\n'; + } + + markdownContent.textContent = `# Learning Report: Community Advocacy & Civic Engagement + +## Course Information +- **Course Code:** CIVICS 2025 +- **Student:** Student +- **Status:** ${userData.summary.overallProgress >= 100 ? 'Completed' : userData.summary.overallProgress > 0 ? 'In Progress' : 'Getting Started'} +- **Generated:** ${new Date(userData.timestamp).toLocaleString()} + +## Summary Statistics +- **Overall Progress:** ${userData.summary.overallProgress}% +- **Total Time Spent:** ${Math.round((Date.now() - (JSON.parse(localStorage.getItem('courseStartTime') || Date.now()))) / 60000)} minutes +- **Sections Completed:** ${[userData.summary.lectureProgress, userData.summary.practiceProgress, userData.summary.discussionProgress].filter(p => p >= 100).length}/3 +- **Total Interactions:** ${userData.summary.totalInteractions} + +## Section Details + +### ๐Ÿ“š Interactive Lecture +- **Progress:** ${userData.summary.lectureProgress}% +- **Status:** ${userData.summary.lectureProgress >= 100 ? 'Completed โœ…' : userData.summary.lectureProgress > 0 ? 'In Progress โณ' : 'Ready to start โญ•'} +- **Multiple Choice Answers:** ${userData.summary.lectureAnswers} +- **Interactions:** ${userData.sections.lecture.length} + +### ๐Ÿ’ฌ Practice & Discussion +- **Progress:** ${userData.summary.practiceProgress}% +- **Status:** ${userData.summary.practiceProgress >= 100 ? 'Completed โœ…' : userData.summary.practiceProgress > 0 ? 'In Progress โณ' : 'Ready to start โญ•'} +- **Text Inputs:** ${userData.summary.practiceInputs} +- **Interactions:** ${userData.sections.practice.length} + +### ๐ŸŽฏ Reflect & Assess +- **Progress:** ${userData.summary.discussionProgress}% +- **Status:** ${userData.summary.discussionProgress >= 100 ? 'Completed โœ…' : userData.summary.discussionProgress > 0 ? 'In Progress โณ' : 'Ready to start โญ•'} +- **Text Inputs:** ${userData.summary.discussionInputs} +- **Interactions:** ${userData.sections.discussion.length} + +${interactionsSection} + +## Performance Analysis +**Completion Rate:** ${userData.summary.overallProgress}% +**Status:** ${userData.summary.overallProgress >= 100 ? 'Course completed! ๐ŸŽ‰' : userData.summary.overallProgress > 50 ? 'Making great progress! ๐Ÿ“ˆ' : userData.summary.overallProgress > 0 ? 'Just getting started. ๐Ÿš€' : 'Ready to begin your learning journey! ๐ŸŒŸ'} + +--- +*Report generated by Course Container System v1.0*`; + } +} +// Enhanced report modal with PDF and Markdown features +function createSimpleReportModal() { + // Remove existing modal if it exists + const existingModal = document.getElementById('simpleReportModal'); + if (existingModal) { + existingModal.remove(); + } + + const modal = document.createElement('div'); + modal.id = 'simpleReportModal'; + modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4'; + modal.style.display = 'none'; + + modal.innerHTML = ` +
+
+

๐Ÿ“Š Learning Progress Report

+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+
๐ŸŽ“
+

+ Community Advocacy & Civic Engagement +

+

Course Code: CIVICS 2025

+
+ + Active Course +
+
+ +
+ +
+
+ + +
+
+
+

โญ• ๐Ÿ“š Interactive Lecture

+ 0% +
+
+
+
+
+
+
+
Time: 0m
+
Status: Ready to start
+
+
+ +
+
+

โญ• ๐Ÿ’ฌ Practice & Discussion

+ 0% +
+
+
+
+
+
+
+
Time: 0m
+
Status: Ready to start
+
+
+ +
+
+

โญ• ๐ŸŽฏ Reflect & Assess

+ 0% +
+
+
+
+
+
+
+
Time: 0m
+
Status: Ready to start
+
+
+
+ + +
+

+ Report generated on ${new Date().toLocaleString()} +

+

+ Complete sections to see detailed progress analytics +

+
+
+
+ + + +
+ + +
+
+ +
+ + + +
+
+
+
+ `; + + document.body.appendChild(modal); + + // Close when clicking outside + modal.addEventListener('click', (e) => { + if (e.target === modal) { + closeSimpleReport(); + } + }); + + // Close with Escape key + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && modal.style.display === 'flex') { + closeSimpleReport(); + } + }); + + return modal; +} + +// Function to close simple report +function closeSimpleReport() { + const modal = document.getElementById('simpleReportModal'); + if (modal) { + modal.style.display = 'none'; + } +} + + // Tab switching function +function switchTab(tabName) { + // Update button states + const tabBtns = document.querySelectorAll('.tab-btn'); + tabBtns.forEach(btn => { + btn.classList.remove('active', 'border-blue-500', 'text-blue-600'); + btn.classList.add('border-transparent', 'text-gray-500'); + }); + + const activeBtn = document.querySelector(`[data-tab="${tabName}"]`); + if (activeBtn) { + activeBtn.classList.add('active', 'border-blue-500', 'text-blue-600'); + activeBtn.classList.remove('border-transparent', 'text-gray-500'); + } + + // Update content visibility + const visualTab = document.getElementById('visualTabContent'); + const markdownTab = document.getElementById('markdownTabContent'); + + if (tabName === 'visual') { + visualTab.classList.remove('hidden'); + markdownTab.classList.add('hidden'); + } else { + visualTab.classList.add('hidden'); + markdownTab.classList.remove('hidden'); + } +} + +// Copy markdown to clipboard +function copyMarkdownReport() { + const markdownContent = document.getElementById('markdownContent'); + const copyBtn = document.getElementById('copyMarkdownBtn'); + + if (markdownContent && copyBtn) { + navigator.clipboard.writeText(markdownContent.textContent).then(() => { + copyBtn.innerHTML = ` + + + + Copied! + `; + setTimeout(() => { + copyBtn.innerHTML = ` + + + + Copy + `; + }, 2000); + }).catch(err => { + alert('Failed to copy to clipboard'); + console.error('Copy failed:', err); + }); + } +} + +// Download markdown file +function downloadMarkdownReport() { + const markdownContent = document.getElementById('markdownContent'); + if (markdownContent) { + const timestamp = new Date().toISOString().split('T')[0]; + const filename = `learning-report-${timestamp}.md`; + + const blob = new Blob([markdownContent.textContent], { type: 'text/markdown' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.style.display = 'none'; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + URL.revokeObjectURL(url); + + // Show notification + if (window.notificationManager) { + window.notificationManager.show(`Downloaded ${filename}`, 'success'); + } else { + alert(`Downloaded ${filename}`); + } + } +} + +// Download PDF (using print dialog) +function downloadPDFReport() { + const timestamp = new Date().toISOString().split('T')[0]; + const printWindow = window.open('', '_blank'); + + // Get the visual report content + const visualContent = document.getElementById('visualTabContent'); + const reportHTML = visualContent ? visualContent.innerHTML : '

No content available

'; + + printWindow.document.write(` + + + + Learning Report - Community Advocacy Course + + + +

Learning Progress Report

+

Generated: ${new Date().toLocaleString()}

+ ${reportHTML} +
+ Report generated by Course Container System +
+ + + `); + + printWindow.document.close(); + + setTimeout(() => { + printWindow.print(); + }, 500); + + // Show notification + if (window.notificationManager) { + window.notificationManager.show('PDF generation started - use browser print dialog', 'info'); + } else { + alert('PDF generation started - use your browser\'s print dialog to save as PDF'); + } +} + +// Download JSON report +function downloadJSONReport() { + const timestamp = new Date().toISOString().split('T')[0]; + const filename = `learning-report-${timestamp}.json`; + + const reportData = { + courseInfo: { + name: 'Community Advocacy & Civic Engagement', + code: 'CIVICS 2025', + generatedAt: new Date().toISOString() + }, + progress: { + overall: 0, + sections: { + lecture: { progress: 0, completed: false }, + practice: { progress: 0, completed: false }, + reflect: { progress: 0, completed: false } + } + }, + statistics: { + timeSpent: 0, + interactions: 0, + sectionsCompleted: 0 + }, + metadata: { + version: '1.0', + platform: 'Course Container System' + } + }; + + const blob = new Blob([JSON.stringify(reportData, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.style.display = 'none'; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + URL.revokeObjectURL(url); + + // Show notification + if (window.notificationManager) { + window.notificationManager.show(`Downloaded ${filename}`, 'success'); + } else { + alert(`Downloaded ${filename}`); + } +} + +// Function to override any content's generateReport function +function overrideContentReportFunctions() { + console.log('๐Ÿ”ง Overriding content report functions...'); + + // Store original if it exists + if (typeof window.generateReport === 'function' && !window.originalGenerateReport) { + window.originalGenerateReport = window.generateReport; + } + +// Our enhanced generateReport function +const enhancedGenerateReport = function() { + console.log('๐Ÿš€ Enhanced generateReport called'); + try { + // Collect user data first + const userData = collectUserData(); + console.log('๐Ÿ“Š User data collected:', userData); + + // Remove any existing modal first + const existingModal = document.getElementById('simpleReportModal'); + if (existingModal) { + existingModal.remove(); + } + + const modal = createSimpleReportModal(); + modal.style.display = 'flex'; + + // Populate with real data after a short delay + setTimeout(() => { + populateReportContent(userData); + console.log('โœ… Report populated with real data'); + }, 100); + + console.log('โœ… Enhanced modal created and shown'); + } catch (error) { + console.error('โŒ Error with enhanced report:', error); + // Fallback to original if our enhanced version fails + if (window.originalGenerateReport && typeof window.originalGenerateReport === 'function') { + console.log('๐Ÿ”„ Falling back to original generateReport'); + window.originalGenerateReport(); + } else { + alert('Report function temporarily unavailable'); + } + } +}; + + // Override both global and window versions + window.generateReport = enhancedGenerateReport; + if (typeof generateReport !== 'undefined') { + generateReport = enhancedGenerateReport; + } + + // Also find and override any report buttons in the DOM + const reportButtons = document.querySelectorAll('button[onclick*="generateReport"], #reportBtn, .report-btn'); + reportButtons.forEach(btn => { + console.log('๐Ÿ”— Found report button:', btn); + // Remove existing onclick + btn.removeAttribute('onclick'); + // Add our enhanced function + btn.onclick = enhancedGenerateReport; + }); + + console.log('โœ… Report functions overridden with enhanced version'); +} + +// Function to set up data collection for interactive content +function setupInteractiveDataCollection(section) { + console.log('๐Ÿ”ง Setting up data collection for:', section); + + // Ensure report button exists + ensureReportButtonExists(); + + if (section === 'lecture') { + setupLectureDataCollection(); + } else if (section === 'practice') { + setupPracticeDataCollection(); + } else if (section === 'discussion') { + setupDiscussionDataCollection(); + } +} + +// Set up data collection for interactive lecture +function setupLectureDataCollection() { + console.log('๐Ÿ“š Setting up lecture data collection...'); + + // Monitor all choice buttons + const choiceButtons = document.querySelectorAll('button[data-choice], .choice-btn, button[onclick*="choice"]'); + choiceButtons.forEach((btn, index) => { + btn.addEventListener('click', function() { + const questionText = this.closest('.question')?.querySelector('h3, .question-text')?.textContent || `Question ${index + 1}`; + const choiceText = this.textContent || this.getAttribute('data-choice') || `Choice ${index + 1}`; + + console.log('๐Ÿ“ MC Answer recorded:', { questionText, choiceText }); + + // Save to our data store + if (!window.interactionData) window.interactionData = {}; + if (!window.interactionData.lecture) window.interactionData.lecture = []; + + window.interactionData.lecture.push({ + type: 'multiple_choice', + question: questionText, + answer: choiceText, + timestamp: new Date().toISOString(), + section: 'lecture' + }); + + // Save to localStorage + localStorage.setItem('courseInteractionData', JSON.stringify(window.interactionData)); + console.log('๐Ÿ’พ Lecture data saved to localStorage'); + }); + }); + + // Monitor performance/progress + const progressElements = document.querySelectorAll('.progress, [data-progress], .score'); + progressElements.forEach(el => { + const observer = new MutationObserver(() => { + const progressText = el.textContent; + if (progressText && progressText.includes('%') || progressText.includes('score')) { + console.log('๐Ÿ“Š Performance recorded:', progressText); + + if (!window.interactionData) window.interactionData = {}; + if (!window.interactionData.lecture) window.interactionData.lecture = []; + + window.interactionData.lecture.push({ + type: 'performance', + data: progressText, + timestamp: new Date().toISOString(), + section: 'lecture' + }); + + localStorage.setItem('courseInteractionData', JSON.stringify(window.interactionData)); + } + }); + + observer.observe(el, { childList: true, subtree: true, characterData: true }); + }); + + console.log('โœ… Lecture data collection active'); +} + +// Set up data collection for practice section +function setupPracticeDataCollection() { + console.log('๐Ÿ‹๏ธ Setting up practice data collection...'); + + // Monitor text inputs and textareas + const textInputs = document.querySelectorAll('input[type="text"], textarea, [contenteditable="true"]'); + textInputs.forEach((input, index) => { + input.addEventListener('input', debounce(function() { + const value = this.value || this.textContent; + if (value && value.trim().length > 2) { + console.log('โœ๏ธ Practice text recorded:', value.substring(0, 50) + '...'); + + if (!window.interactionData) window.interactionData = {}; + if (!window.interactionData.practice) window.interactionData.practice = []; + + // Update or add the entry + const existingIndex = window.interactionData.practice.findIndex(item => + item.inputId === (this.id || `input-${index}`) + ); + + const entry = { + type: 'text_input', + inputId: this.id || `input-${index}`, + content: value, + timestamp: new Date().toISOString(), + section: 'practice' + }; + + if (existingIndex !== -1) { + window.interactionData.practice[existingIndex] = entry; + } else { + window.interactionData.practice.push(entry); + } + + localStorage.setItem('courseInteractionData', JSON.stringify(window.interactionData)); + } + }, 1000)); + }); + + console.log('โœ… Practice data collection active'); +} + +// Set up data collection for discussion section +function setupDiscussionDataCollection() { + console.log('๐Ÿ’ฌ Setting up discussion data collection...'); + + // Monitor text inputs and textareas + const textInputs = document.querySelectorAll('input[type="text"], textarea, [contenteditable="true"]'); + textInputs.forEach((input, index) => { + input.addEventListener('input', debounce(function() { + const value = this.value || this.textContent; + if (value && value.trim().length > 2) { + console.log('๐Ÿ’ฌ Discussion text recorded:', value.substring(0, 50) + '...'); + + if (!window.interactionData) window.interactionData = {}; + if (!window.interactionData.discussion) window.interactionData.discussion = []; + + // Update or add the entry + const existingIndex = window.interactionData.discussion.findIndex(item => + item.inputId === (this.id || `input-${index}`) + ); + + const entry = { + type: 'text_input', + inputId: this.id || `input-${index}`, + content: value, + timestamp: new Date().toISOString(), + section: 'discussion' + }; + + if (existingIndex !== -1) { + window.interactionData.discussion[existingIndex] = entry; + } else { + window.interactionData.discussion.push(entry); + } + + localStorage.setItem('courseInteractionData', JSON.stringify(window.interactionData)); + } + }, 1000)); + }); + + console.log('โœ… Discussion data collection active'); +} + +// Function to ensure Generate Report button exists in all sections +function ensureReportButtonExists() { + console.log('๐Ÿ”ง Ensuring Generate Report button exists...'); + + // Check if button already exists + let reportBtn = document.querySelector('#reportBtn, .report-btn, button[onclick*="generateReport"]'); + + if (!reportBtn) { + console.log('โž• Creating Generate Report button...'); + + // Create the button + reportBtn = document.createElement('button'); + reportBtn.id = 'reportBtn'; + reportBtn.className = 'bg-primary hover:bg-primary-dark text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors'; + reportBtn.innerHTML = ` + + + + Generate Report + `; + reportBtn.onclick = generateReport; + + // Find a good place to insert it + const contentArea = document.getElementById('contentArea'); + if (contentArea) { + // Try to find a container or just append to content area + const container = contentArea.querySelector('.container, .content, .main') || contentArea; + + // Create a button container + const buttonContainer = document.createElement('div'); + buttonContainer.className = 'mt-6 flex justify-center'; + buttonContainer.appendChild(reportBtn); + + container.appendChild(buttonContainer); + console.log('โœ… Generate Report button added to page'); + } + } else { + // Button exists, make sure it uses our enhanced function + reportBtn.onclick = generateReport; + console.log('โœ… Generate Report button found and enhanced'); + } +} + +// Debounce function to avoid too many saves +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +// Override immediately +overrideContentReportFunctions(); + +// Override after content loads +document.addEventListener('DOMContentLoaded', () => { + setTimeout(overrideContentReportFunctions, 500); +}); + +// Override after any new content is loaded +setTimeout(overrideContentReportFunctions, 2000); +setTimeout(overrideContentReportFunctions, 5000); +console.log('๐Ÿ“ฆ Course App loaded successfully'); \ No newline at end of file diff --git a/gcap3056_extract/GCAP3056/week12-13/index.html b/gcap3056_extract/GCAP3056/week12-13/index.html new file mode 100644 index 00000000..e69de29b diff --git a/gcap3056_extract/GCAP3056/week4-5/index.html b/gcap3056_extract/GCAP3056/week4-5/index.html new file mode 100644 index 00000000..e69de29b diff --git a/gcap3056_extract/GCAP3056/week6-7/index.html b/gcap3056_extract/GCAP3056/week6-7/index.html new file mode 100644 index 00000000..e69de29b diff --git a/gcap3056_extract/GCAP3056/week8-11/index.html b/gcap3056_extract/GCAP3056/week8-11/index.html new file mode 100644 index 00000000..e69de29b diff --git a/gcap3056_extract/Wheeldecide/iframes/GCAP3056.html b/gcap3056_extract/Wheeldecide/iframes/GCAP3056.html new file mode 100644 index 00000000..ec87aeb1 --- /dev/null +++ b/gcap3056_extract/Wheeldecide/iframes/GCAP3056.html @@ -0,0 +1 @@ + \ No newline at end of file