File size: 22,472 Bytes
0c140a6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>Interconnected Subject Explorer</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  <style>

    html.dark { background: #1a2233; }

    ::selection { background: #fbbf24; color: #1a2233; }

    .fade-in { animation: fadeIn .5s; }

    @keyframes fadeIn { from { opacity:0; transform: translateY(24px);} to {opacity:1; transform:none;} }

    .glass {

      background: rgba(255,255,255,0.82);

      backdrop-filter: blur(7px) saturate(1.2);

    }

    html.dark .glass {

      background: rgba(28,32,46,0.90);

      color: #f1f5f9;

    }

    .scenario-active {

      outline: 3px solid #6366f1;

      box-shadow: 0 2px 24px 0 #6366f140;

      z-index: 10;

    }

  </style>
</head>
<body class="bg-slate-100 dark:bg-slate-900">
<div id="root"></div>
<script type="text/babel">

const { useState, useMemo, useEffect, useCallback } = React;



const SUBJECTS = [

  { key: "math",    name: "Mathematics", icon: "➗", color: "bg-indigo-500", accent: "from-indigo-400 to-fuchsia-500" },

  { key: "science", name: "Science",     icon: "🔬", color: "bg-green-600",  accent: "from-green-400 to-cyan-500" },

  { key: "history", name: "History",     icon: "📜", color: "bg-yellow-600", accent: "from-yellow-400 to-orange-400" },

  { key: "art",     name: "Art",         icon: "🎨", color: "bg-pink-500",   accent: "from-pink-400 to-rose-400" },

  { key: "geography", name: "Geography", icon: "🗺️", color: "bg-blue-500",  accent: "from-blue-400 to-green-300" },

  { key: "cs", name: "Computer Science", icon: "💻", color: "bg-slate-700",  accent: "from-slate-600 to-emerald-400" },

  { key: "economics", name: "Economics", icon: "💹", color: "bg-orange-600", accent: "from-orange-400 to-yellow-400" },

  { key: "literature", name: "Literature", icon: "📚", color: "bg-red-500", accent: "from-red-400 to-yellow-300" }

];



function darkModeInit() {

  const mql = window.matchMedia("(prefers-color-scheme: dark)");

  if (mql.matches) document.documentElement.classList.add('dark');

}

darkModeInit();



function App() {

  const [apiKey, setApiKey] = useState("");

  const [currentSubject, setCurrentSubject] = useState(SUBJECTS[0].key);

  const [aiLoading, setAiLoading] = useState(false);

  const [aiError, setAiError] = useState("");

  const [aiData, setAiData] = useState({});

  const [inputPrompt, setInputPrompt] = useState("");

  const [activeScenario, setActiveScenario] = useState(null);

  const [projectNotes, setProjectNotes] = useState("");

  const [pbLoading, setPbLoading] = useState(false);

  const [pbError, setPbError] = useState("");

  const [pbResult, setPbResult] = useState("");

  // Per-subject "What I'm studying this week"

  const [subjectNotes, setSubjectNotes] = useState({});



  const subjectMeta = useMemo(() => SUBJECTS.find(s=>s.key===currentSubject), [currentSubject]);

  const currentSubjectNote = subjectNotes[currentSubject] || "";



  // AI request handler (for ideas/scenarios)

  const fetchAIContent = useCallback(async () => {

    if (!apiKey || !apiKey.startsWith("sk-")) { setAiError("Please enter a valid OpenAI API key."); return; }

    setAiLoading(true); setAiError(""); setActiveScenario(null);

    const extraContext = [

      inputPrompt ? inputPrompt : null,

      currentSubjectNote ? `This week, the student is studying: "${currentSubjectNote}" in ${subjectMeta.name}.` : null

    ].filter(Boolean).join(" ");

    const prompt = `

You are an educational AI agent. For the subject "${subjectMeta.name}", perform the following:

1. Generate five engaging, critical-thinking ideas or inquiry questions for students, encouraging them to explore and analyze complex concepts within this subject. Each should be a concise, thought-provoking prompt.

2. Propose nine creative real-life scenarios, each showing how "${subjectMeta.name}" connects with at least one other school subject (from: Mathematics, Science, History, Art, Geography, Computer Science, Economics, Literature), and describe a real-world application for each. Clearly specify which subjects are involved.



${extraContext ? "User context: " + extraContext : ""}



Format the result as valid JSON with two top-level keys:

- "ideas": array of strings (five items).

- "scenarios": array of objects, each with:

   - "title": string,

   - "subjects": array of strings (subject names),

   - "application": string (practical real-life connection).



Output ONLY the JSON, no commentary.

    `.trim();

    try {

      const res = await fetch("https://api.openai.com/v1/chat/completions", {

        method: "POST",

        headers: {

          "Content-Type": "application/json",

          Authorization: `Bearer ${apiKey}`

        },

        body: JSON.stringify({

          model: "gpt-4o-mini",

          messages: [

            { role: "system", content: "You are a helpful educational agent. Only return valid JSON, no markdown or commentary." },

            { role: "user", content: prompt }

          ],

          temperature: 0.74,

          max_tokens: 1200

        })

      });

      if (!res.ok) throw new Error(`API error (${res.status})`);

      const data = await res.json();

      const txt = data.choices[0].message.content;

      const start = txt.indexOf("{"), end = txt.lastIndexOf("}");

      const jsonString = (start !== -1 && end !== -1) ? txt.slice(start, end+1) : "";

      const result = JSON.parse(jsonString);

      setAiData(prev => ({...prev, [currentSubject]: result}));

    } catch (e) {

      setAiError("Failed to load AI ideas: " + (e.message || "Unknown error"));

    } finally { setAiLoading(false); }

  }, [apiKey, currentSubject, subjectMeta, inputPrompt, currentSubjectNote]);



  useEffect(() => {

    setAiError(""); setActiveScenario(null); setProjectNotes("");

    setPbError(""); setPbResult(""); setPbLoading(false);

  }, [currentSubject]);



  useEffect(()=>{

    function toggle(e){

      if(e.ctrlKey && e.key==="k"){

        document.documentElement.classList.toggle("dark");

        e.preventDefault();

      }

    }

    window.addEventListener("keydown", toggle);

    return ()=>window.removeEventListener("keydown",toggle);

  },[]);



  // Canvas content for each subject

  const canvasContent = useMemo(() => {

    const d = aiData[currentSubject];

    return (

      <div className="fade-in px-1 sm:px-8 pt-1 pb-4">

        {/* User custom input for the subject */}

        <div className="mb-6">

          <label className="block font-semibold text-indigo-800 dark:text-indigo-200 mb-1">

            What are you studying in {subjectMeta.name} this week?

          </label>

          <input

            type="text"

            className="w-full px-3 py-2 rounded-lg border bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600 shadow text-base focus:ring-2 focus:ring-indigo-400"

            placeholder={`E.g., Algebraic fractions, Newton's Laws, Ancient Egypt, Poetry, ...`}

            value={currentSubjectNote}

            onChange={e=>{

              setSubjectNotes(notes => ({

                ...notes,

                [currentSubject]: e.target.value

              }));

            }}

            aria-label={`What are you studying in ${subjectMeta.name} this week?`}

          />

        </div>

        {/* Ideas */}

        {d && (

          <div className="mb-7">

            <h3 className="text-2xl font-semibold mb-2 tracking-tight text-indigo-900 dark:text-indigo-200">

              Ideas to Explore (Critical Thinking)

            </h3>

            <ul className="grid gap-2">

              {d.ideas.map((idea, i) =>

                <li key={i}

                  className="rounded-xl px-4 py-2 bg-gradient-to-br from-white via-indigo-50/60 to-indigo-100 dark:from-slate-800 dark:via-slate-700 dark:to-indigo-900/40 shadow-sm border-l-4 border-indigo-400 dark:border-indigo-500">

                  {idea}

                </li>

              )}

            </ul>

          </div>

        )}

        {/* Scenarios */}

        {d && (

        <div>

          <h3 className="text-2xl font-semibold mb-2 tracking-tight text-emerald-900 dark:text-emerald-200">

            Interconnected Scenarios (Real-Life Applications)

          </h3>

          <ul className="grid gap-3">

            {d.scenarios.map((sc, i) =>

              <li key={i}

                  className={

                    "glass rounded-xl px-5 py-4 shadow transition hover:scale-[1.025] cursor-pointer " +

                    (activeScenario === i ? "scenario-active" : "")

                  }

                  onClick={()=>{setActiveScenario(i); setProjectNotes(""); setPbResult(""); setPbError(""); setPbLoading(false);}}

                  tabIndex={0}

                  aria-label={`Activate scenario ${i+1}: ${sc.title}`}>

                <div className="flex items-center mb-1">

                  <span className="font-bold text-lg">{sc.title}</span>

                  <span className="ml-3 flex flex-wrap gap-1 text-xs">

                    {sc.subjects.map((s,j) =>

                      <span key={j}

                        className={`inline-block rounded px-2 py-0.5 font-semibold ${{

                          Mathematics:"bg-indigo-200 text-indigo-900 dark:bg-indigo-700 dark:text-indigo-100",

                          Science:"bg-green-200 text-green-900 dark:bg-green-700 dark:text-green-100",

                          History:"bg-yellow-200 text-yellow-900 dark:bg-yellow-700 dark:text-yellow-100",

                          Art:"bg-pink-200 text-pink-900 dark:bg-pink-700 dark:text-pink-100",

                          Geography:"bg-blue-200 text-blue-900 dark:bg-blue-700 dark:text-blue-100",

                          "Computer Science":"bg-slate-300 text-slate-900 dark:bg-slate-700 dark:text-slate-100",

                          Economics:"bg-orange-200 text-orange-900 dark:bg-orange-700 dark:text-orange-100",

                          Literature:"bg-red-200 text-red-900 dark:bg-red-700 dark:text-red-100"

                        }[s]||"bg-slate-200 text-slate-700 dark:bg-slate-600 dark:text-slate-200"}`}>{s}</span>

                    )}

                  </span>

                </div>

                <div className="pl-1 text-base leading-snug text-slate-800 dark:text-slate-200">{sc.application}</div>

              </li>

            )}

          </ul>

        </div>

        )}

        {!d && (

          <div className="text-lg text-center mt-12 text-slate-500 dark:text-slate-300">

            <div className="animate-pulse">Ask AI for critical thinking ideas and scenarios!</div>

          </div>

        )}

      </div>

    );

  }, [aiData, currentSubject, activeScenario, subjectMeta, subjectNotes, currentSubjectNote]);



  // Project-Based Learning section (shows when a scenario is active)

  function ProjectPanel() {

    const d = aiData[currentSubject];

    if (!d || activeScenario==null || !d.scenarios[activeScenario]) return null;

    const sc = d.scenarios[activeScenario];



    // Handler for project-based learning AI request

    async function askAIForProject() {

      if (!apiKey || !apiKey.startsWith("sk-")) { setPbError("Please enter a valid OpenAI API key."); return; }

      setPbLoading(true); setPbError(""); setPbResult("");

      const thisWeekNote = subjectNotes[currentSubject] ? `This week, the student is studying: "${subjectNotes[currentSubject]}" in ${subjectMeta.name}.` : "";

      const prompt = `

A student has chosen the following scenario for a project-based learning activity:

Title: ${sc.title}

Subjects Involved: ${sc.subjects.join(", ")}

Real-Life Application: ${sc.application}

${thisWeekNote}



Create a detailed, multi-step project-based learning activity that:

- Guides the student through a meaningful investigation or creation process related to this scenario.

- Specifies a driving question, expected outcomes, and assessment ideas.

- Incorporates collaboration, creativity, real-world research, and presentation or product.

- Is practical for students in middle or high school.



Format your answer as clear markdown with sections: **Driving Question**, **Project Steps**, **Expected Outcomes**, **Assessment Ideas**.

      `.trim();

      try {

        const res = await fetch("https://api.openai.com/v1/chat/completions", {

          method: "POST",

          headers: {

            "Content-Type": "application/json",

            Authorization: `Bearer ${apiKey}`

          },

          body: JSON.stringify({

            model: "gpt-4o-mini",

            messages: [

              { role: "system", content: "You are an expert in project-based learning. Return clear, structured markdown only." },

              { role: "user", content: prompt }

            ],

            temperature: 0.72,

            max_tokens: 650

          })

        });

        if (!res.ok) throw new Error(`API error (${res.status})`);

        const data = await res.json();

        let answer = data.choices[0].message.content.trim();

        if (answer.startsWith("```")) answer = answer.replace(/^```[a-z]*\s*/,'').replace(/```$/,'');

        setPbResult(answer);

      } catch (e) {

        setPbError("Failed to load project activity: " + (e.message || "Unknown error"));

      } finally { setPbLoading(false); }

    }



    return (

      <div className="fade-in mt-8 mb-6 max-w-2xl mx-auto glass p-6 rounded-3xl border border-indigo-300 dark:border-indigo-700 shadow-xl">

        <h3 className="text-2xl font-bold mb-2 text-fuchsia-700 dark:text-fuchsia-200">Project-Based Learning: <span className="text-indigo-900 dark:text-indigo-200">{sc.title}</span></h3>

        <div className="mb-2 text-base font-semibold text-emerald-700 dark:text-emerald-200">

          Subjects Involved:

          <span className="ml-2">{sc.subjects.join(", ")}</span>

        </div>

        <div className="mb-2 text-base">

          <span className="font-semibold text-indigo-700 dark:text-indigo-300">Real-Life Connection:</span> {sc.application}

        </div>

        <div className="mb-4 text-slate-700 dark:text-slate-300">

          <span className="font-semibold">Project Steps & Tips:</span>

          <ol className="list-decimal ml-6 mt-1 space-y-1">

            <li>Define the main question/problem you want to solve, inspired by the scenario.</li>

            <li>Research how the connected subjects apply to the scenario—collect data, examples, or cases.</li>

            <li>Design an experiment, model, creative product, or presentation that integrates concepts from each subject.</li>

            <li>Document your findings with evidence, analysis, and reflections.</li>

            <li>Share your project with peers or your teacher—get feedback and discuss real-world impacts.</li>

          </ol>

        </div>

        <textarea

          className="w-full min-h-[80px] rounded-lg border px-3 py-2 bg-white/90 dark:bg-slate-800 border-indigo-200 dark:border-indigo-500 shadow text-base mb-3"

          placeholder="Use this space to brainstorm ideas, outline your project, or write your project plan..."

          value={projectNotes}

          onChange={e=>setProjectNotes(e.target.value)}

        />

        <div className="flex flex-wrap gap-3 mt-1 mb-3">

          <button

            className="px-4 py-1 rounded-lg font-bold text-white bg-fuchsia-600 hover:bg-fuchsia-700 shadow transition"

            onClick={()=>setProjectNotes("")}

          >Clear Notes</button>

          <button

            className="px-4 py-1 rounded-lg font-bold bg-indigo-200 text-indigo-900 dark:bg-indigo-700 dark:text-indigo-100 shadow"

            onClick={()=>setActiveScenario(null)}

          >Close Project</button>

          <button

            className="px-4 py-1 rounded-lg font-bold bg-emerald-500 text-white hover:bg-emerald-600 shadow transition"

            onClick={askAIForProject}

            disabled={pbLoading}

            aria-label="Ask AI for a detailed project-based learning activity"

          >

            {pbLoading ? <span className="animate-pulse">Thinking...</span> : "Ask AI for a Project Plan"}

          </button>

        </div>

        {pbError && <div className="mb-2 px-3 py-2 rounded bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-200 font-semibold text-center">{pbError}</div>}

        {pbResult && (

          <div className="prose prose-indigo dark:prose-invert max-w-full mt-4 border-t pt-4">

            <MarkdownRenderer markdown={pbResult}/>

          </div>

        )}

      </div>

    );

  }



  function MarkdownRenderer({markdown}) {

    let html = markdown

      .replace(/(^|\n)### (.*)/g, '$1<h3>$2</h3>')

      .replace(/(^|\n)## (.*)/g, '$1<h2>$2</h2>')

      .replace(/(^|\n)# (.*)/g, '$1<h1>$2</h1>')

      .replace(/\*\*(.*?)\*\*/g, '<b>$1</b>')

      .replace(/\*(.*?)\*/g, '<i>$1</i>')

      .replace(/^\s*-\s+(.*)$/gm, '<ul><li>$1</li></ul>')

      .replace(/^\s*\d+\.\s+(.*)$/gm, '<ol><li>$1</li></ol>')

      .replace(/\n{2,}/g, '<br/><br/>');

    html = html.replace(/<\/ul>\s*<ul>/g, '');

    html = html.replace(/<\/ol>\s*<ol>/g, '');

    return <div dangerouslySetInnerHTML={{__html: html}} />;

  }



  function SubjectTabs() {

    return (

      <div className="flex flex-wrap gap-2 mb-4 sm:mb-6">

        {SUBJECTS.map(s =>

          <button key={s.key}

            className={

              "group flex items-center px-4 py-2 rounded-2xl shadow-sm border-2 focus:outline-none text-lg font-semibold tracking-tight transition-all duration-150 " +

              (currentSubject===s.key

                ? `${s.color} bg-gradient-to-r ${s.accent} border-transparent text-white scale-105`

                : "bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-700 text-slate-900 dark:text-slate-200 hover:scale-105 hover:border-indigo-400 dark:hover:border-indigo-300")

            }

            onClick={() => setCurrentSubject(s.key)}

            aria-label={`Switch to ${s.name}`}

          >

            <span className="mr-2 text-2xl">{s.icon}</span> {s.name}

          </button>

        )}

      </div>

    );

  }



  return (

    <div className="min-h-screen flex flex-col items-center pb-16 bg-gradient-to-br from-indigo-100 via-slate-100 to-pink-50 dark:from-slate-900 dark:via-slate-900 dark:to-indigo-950 transition-colors duration-300">

      <header className="w-full max-w-4xl mx-auto pt-10 pb-4">

        <h1 className="text-3xl sm:text-4xl md:text-5xl font-extrabold mb-2 text-slate-800 dark:text-white tracking-tight text-center">

          Interconnected Subject Explorer

        </h1>

        <p className="text-lg sm:text-xl text-center max-w-2xl mx-auto text-slate-600 dark:text-slate-300">

          Explore how your school subjects connect and find real-world meaning—with interactive canvases and instant AI-powered scenarios.

        </p>

      </header>

      <main className="w-full max-w-4xl flex-1 fade-in">

        <section className="rounded-3xl glass shadow-lg p-4 sm:p-8">

          <div className="flex flex-wrap items-center gap-3 mb-6">

            <input

              type="password"

              className="flex-1 px-4 py-2 rounded-lg border bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600 shadow text-base focus:ring-2 focus:ring-indigo-400"

              style={{minWidth:160, maxWidth:290}}

              placeholder="OpenAI API key (sk-...)" value={apiKey}

              onChange={e=>setApiKey(e.target.value)}

              aria-label="OpenAI API Key"

            />

            <input

              type="text"

              className="flex-1 px-4 py-2 rounded-lg border bg-slate-50 dark:bg-slate-800 border-slate-300 dark:border-slate-600 shadow text-base focus:ring-2 focus:ring-indigo-400"

              style={{minWidth:160, maxWidth:300}}

              placeholder="Add an extra prompt (optional)" value={inputPrompt}

              onChange={e=>setInputPrompt(e.target.value)}

              aria-label="Additional prompt for AI"

            />

            <button

              className={`px-6 py-2 rounded-xl font-bold text-lg shadow transition-all

                bg-indigo-600 hover:bg-indigo-700 text-white ${aiLoading ? "opacity-50" : ""}`}

              disabled={aiLoading || !apiKey}

              onClick={fetchAIContent}

              aria-label="Generate ideas and scenarios with AI"

            >

              {aiLoading

                ? <span className="animate-pulse flex items-center gap-2">

                    <svg className="w-5 h-5 mr-1 animate-spin" fill="none" viewBox="0 0 24 24">

                      <circle className="opacity-20" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"/>

                      <path className="opacity-90" fill="currentColor" d="M4 12a8 8 0 018-8v8z"/>

                    </svg>

                    Thinking...

                  </span>

                : "Ask AI"

              }

            </button>

          </div>

          <SubjectTabs/>

          <div className="min-h-[320px]">{canvasContent}</div>

          {aiError && <div className="mt-6 px-3 py-2 rounded bg-red-100 dark:bg-red-900 text-red-700 dark:text-red-200 font-semibold text-center">{aiError}</div>}

          {ProjectPanel()}

        </section>

      </main>

      <footer className="w-full max-w-3xl text-center mt-10 mb-6 text-slate-500 dark:text-slate-400 text-xs">

        <div>

          <span className="font-bold">Tip:</span> Press <kbd>Ctrl</kbd>+<kbd>K</kbd> to toggle dark mode. <span className="ml-2">No API keys are stored.</span>

        </div>

        <div className="mt-1">

          Made for educational exploration. &copy; {new Date().getFullYear()}

        </div>

      </footer>

    </div>

  );

}



ReactDOM.createRoot(document.getElementById("root")).render(<App />);

</script>
</body>
</html>