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 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
+
+
+
+
+
+
๐ฏ 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.
+
+
+
+
+
+
+ ๐ Show Shared Google Doc (Course Notes & Syllabus)
+
+
+
+
+
+
+
+
+
+
+
+
+ ๐
Show Course Roadmap & Notes
+
+
+
+
+
+
+
+
+
+
+
๐
Course Roadmap & Module Notes
+
+
+
+
+
+
+
+
+
+
+
+
1-3
+
+
Orientation & Issue Discovery
+
+ Course intro, JRE case study, civil service careers.
+ Brainstorm project ideas; form teams; intro to Legco/SCMP writing.
+
+
View Notes
+
+
+
+
+
4-5
+
+
Data & Info Gathering
+
+ Code on Access to Info, drafting requests.
+ Gather background data, start info requests.
+
+
View Notes
+
+
+
+
+
6-7
+
+
Analysis & Writing Foundations
+
+ Seminar on "vibe coding" (tentative), field trip (tentative).
+ Legco report writing workshop, draft arguments.
+
+
View Notes
+
+
+
+
+
8-11
+
+
Argument & Engagement
+
+ Refine arguments, draft Legco reports & SCMP letters.
+ SCMP/JRE/Legco workshops.
+ Social media engagement & storytelling.
+
+
View Notes
+
+
+
+
+
12-13
+
+
Finalization & Reflection
+
+ Finalize and submit reports, letters, portfolios.
+ Reflective journal, peer review, optional showcase.
+
+
View Notes
+
+
+
+
+
+
+
+
+
+
+
+
๐ Assessment Guide
+
+ Research Paper
+ Engagement Portfolio
+ Reflective Journal
+
+
+
+
+
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
+
+
+
+
+
+
Community Engagement Portfolio (40%)
+
+ Group work; 1000-1500 words per member (or multimedia equivalent)
+ Includes: info requests, public writing (letters/reports), infographic/video, AI usage account
+ Draft by Week 10, final by Week 12
+
+
+
+
+
+
Reflective Learning Journal (35%)
+
+ Individual work; ~5 entries, each ~300 words (with AI chat history)
+ Written with Gen AI chatbot (GCAPdiscuss4students)
+ Submit in Google Docs (provided by teacher)
+
+
+
+
+
+
+
+
+
+
+
๐ 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%
+ Report
+
+
+
+
+
+
+
+
+
+ โ๏ธ
+
+
+
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.
+
+
+
+
+ ๐ Show detailed instructions
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
Your Response:
+
+
+ Characters: 0 | Words: 0
+
+
+
+
+
+
+
+
+ ๐ฅ Peer Discussion
+
+
+ Share your experience with classmates and learn from their perspectives.
+
+
+ ๐ Get discussion prompts
+
+
+
+
+
+
+ ๐ค AI Feedback
+
+
+ Get personalized feedback on your reflection.
+
+
+ โจ Generate AI prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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?
+
+
+
+ ๐ Show detailed instructions
+
+
+
+
+
+
+
+
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?
+
+
+
+
+
+
+
Your Action Plan:
+
+
+ Characters: 0 | Words: 0
+
+
+
+
+
+
+ ๐ฅ Peer Discussion
+
+
+ Compare strategies and learn from different approaches.
+
+
+ ๐ Get discussion prompts
+
+
+
+
+
+ ๐ค AI Feedback
+
+
+ Refine your strategy with AI suggestions.
+
+
+ โจ Generate AI prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+
Professional Communication
+
+
โญ
+
+
+
+
+ Write a professional email or letter to the appropriate authority about your problem. Focus on clarity, evidence, and constructive tone.
+
+
+
+ ๐ Show writing guidelines
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
Your Professional Communication:
+
+
+ Characters: 0 | Words: 0
+
+
+
+
+
+
+ ๐ฅ Peer Review
+
+
+ Get feedback on tone, clarity, and effectiveness.
+
+
+ ๐ Get review prompts
+
+
+
+
+
+ ๐ค AI Review
+
+
+ Improve your professional communication.
+
+
+ โจ Generate review prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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?
+
+
+
+ ๐ Show reflection guide
+
+
+
+
+
+
+
+
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?
+
+
+
+
+
+
+
Your Learning Synthesis:
+
+
+ Characters: 0 | Words: 0
+
+
+
+
+
+
+ ๐ฅ Peer Discussion
+
+
+ Share insights and learn from diverse perspectives.
+
+
+ ๐ Get discussion prompts
+
+
+
+
+
+ ๐ค AI Feedback
+
+
+ Deepen your reflection with AI guidance.
+
+
+ โจ Generate AI prompt
+
+
+
+
+
+
+
+
+
+
+
+
+ ๐
+
+
Practice Tasks Complete!
+
+ Excellent work! You've completed all writing tasks and are ready to move forward in your learning journey.
+
+
+
๐ Words written: 0
+
โฑ๏ธ Time spent: 0 min
+
+
+
+
+
+
+
\ 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
+
+
+ Your Reflection:
+
+
+
+
+
+
+
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.
+
+
+
+
+
+
+ Mark as Complete
+
+
+
+
+
+
+
+
\ 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
+
+
+
+
+
+
+
+
+
+ ๐
+ Interactive Lecture
+
+ โญ Not Started
+
+ What is policy? Who are the actors? Why advocacy matters?
+
+
+
+
+
+
+ ๐ฌ
+ Practice & Discussion
+
+ โญ Not Started
+
+ Try your first โpolicy askโ and review classmatesโ ideas!
+
+
+
+
+
+
+ ๐ฏ
+ Reflect & Assess
+
+ โญ Not Started
+
+ Set goals and reflect on your advocacy journey.
+
+
+
+
+
+
+
+
+
๐
+
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.
+
+
+
+
+
+
+
+
+
+
+ Save Progress
+
+
+
+
+
+ Generate Report
+
+
+ ๐ Toggle Dark Mode
+
+
+
+
+
+ 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?
+
+
+ A. Government is always bad and should be abolished.
+
+
+ B. Government can limit our freedom, but is also needed to keep society safe and fair.
+
+
+ C. Only dictatorships are evil; democracies are never a problem.
+
+
+
+
+
+
+
True or False: In a modern society, we can do without any government at all.
+
+ True
+ False
+
+
+
+
+
+
+
+
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 __________.
+
+
Check
+
+
+
+ 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?
+
+
+ A. Open access to government records and reporting in the media.
+
+
+ B. Allowing only official government statements in the news.
+
+
+
+
+
+
+
+
+
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?
+
+
+ A. Give up and accept the refusal.
+
+
+ B. Ask for a review or make a complaint.
+
+
+ C. Wait for one year and ask again.
+
+
+
+
+
+
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)
+
+ VIDEO
+
+
+ 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.).
+
+
+ Your Reflection: Whatโs missing from the official video?
+
+ Submit Reflection
+
+
+
+
+
+
+
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!
+
+
+
+
+
+
+
+ โถ๏ธ
+ Read: Simon Wangโs Letter for SCMPโs 120th Anniversary
+
+
+
Letters to the South China Morning Post bridge the space between the public and the ivory tower
+
Published: 12:30pm, 6 Nov 2023
+
As a frequent contributor to this column, I wish to congratulate the South China Morning Post on its 120th anniversary and thank the editors for their support for my research and advocacy work in the past 10 years.
+
I started writing to the Letters section in 2013 and have published over 100 letters. Since 2020, I have also been working with my colleague, Dr Benedict Rowlett, at Baptist University, helping over 300 students to publish nearly 140 letters.
+
My first published letter to the editor was on Creative Commons, an alternative approach to copyright management. In my first featured letter in 2016, I made the case for Hong Kong to phase out Octopus cards and imagined a smart city embracing diverse mobile payment methods. With the advent of generative artificial intelligence, my students and I also published the first letter in SCMPโs history that was edited by ChatGPT. An overarching theme of most of our letters is to question the status quo and envision a better world made possible by new technologies.
+
The letters column offers us opportunities to critically review the decisions made by the Hong Kong government. Through researching public policy matters, including citing the Code on Access to Information, we have published letters on a wide range of issues such as the emergency alert system, national security law and solar weather. Occasionally, government staff, for example, the Department of Justice and Hong Kong Observatory, would write in response.
+
Our conversation with the government extended beyond the newspaper. We met Legislative Council members including Chan Kin-por, Alice Mak Mei-kuen, Regina Ip Lau Suk-yee and Tse Wai-chuen, and brought their attention to the challenges facing groups whose voices are less often heard. The letters section lets us share our conversations with lawmakers and create additional pressure for government action.
+
It is also a bridge between the public and the ivory tower. I have co-authored letters with research students to share insights from Baptist University research teams on Chinese medicine and carbon emission monitoring. Recently, Dr Rowlett and I have received support from the General Research Fund to study how citizenship has been exercised by SCMP letter writers. This research could never have been realised without the unwavering commitment of SCMP editors to nurturing a dynamic community of readers and cultivating an invaluable platform for public discourse.
+
+
Simon H. Wang , lecturer in English, Baptist University
+
Read on SCMP
+
+
+
+
+
+
+ Mark Section Complete
+
+
+
+
+
+
\ 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)}
+
+
+
+
+
+
+
+
+ `;
+
+ 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
+
+
+ Complete Section
+
+
+
+ `,
+ 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
+
+
+ Complete Section
+
+
+
+ `,
+ 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
+
+
+ Complete Section
+
+
+
+ `
+ };
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+
+ Download 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
+
+
+
+
+
+
+
+
+
+
+
+ ๐ Visual Report
+
+
+ ๐ Markdown
+
+
+
+
+
+
+
+
+
+
+
+
+
+
๐
+
+ 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
+
+
+
+
+
+
+
+
+
+
Markdown Report
+
+
+
+
+ Copy
+
+
+
# Learning Report: Community Advocacy & Civic Engagement
+
+## Course Information
+- **Course Code:** CIVICS 2025
+- **Student:** Student
+- **Status:** Getting Started
+- **Generated:** ${new Date().toLocaleString()}
+
+## Summary Statistics
+- **Overall Progress:** 0%
+- **Total Time Spent:** 0 minutes
+- **Sections Completed:** 0/3
+- **Total Interactions:** 0
+
+## Section Details
+
+### ๐ Interactive Lecture
+- **Progress:** 0%
+- **Status:** Ready to start โญ
+- **Time Spent:** 0 minutes
+- **Interactions:** 0
+
+### ๐ฌ Practice & Discussion
+- **Progress:** 0%
+- **Status:** Ready to start โญ
+- **Time Spent:** 0 minutes
+- **Interactions:** 0
+
+### ๐ฏ Reflect & Assess
+- **Progress:** 0%
+- **Status:** Ready to start โญ
+- **Time Spent:** 0 minutes
+- **Interactions:** 0
+
+## Learning Timeline
+- **${new Date().toLocaleString()}:** Report generated
+
+## Performance Analysis
+**Completion Rate:** 0%
+**Status:** Just getting started. ๐
+
+---
+*Report generated by Course Container System v1.0*
+
+
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+ Download MD
+
+
+
+
+
+ Download PDF
+
+
+
+
+
+ Download JSON
+
+
+
+
+
+ `;
+
+ 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