403Webshell
Server IP : 69.72.244.102  /  Your IP : 216.73.216.164
Web Server : LiteSpeed
System : Linux s3434.fra1.stableserver.net 4.18.0-513.24.1.lve.2.el8.x86_64 #1 SMP Fri May 24 12:42:50 UTC 2024 x86_64
User : konzalta ( 1271)
PHP Version : 7.4.33
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /home/konzalta/exams.konzaltant.com/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /home/konzalta/exams.konzaltant.com/index.jsx
import { useState, useEffect, useMemo } from "react";
import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine } from "recharts";

// ---------- palette: morning light ----------
const C = {
  paper: "#FDF7ED", paperDeep: "#F8EFDF", card: "#FFFFFF",
  rule: "#EEE3D0", ink: "#33393E",
  teal: "#2E8B7A", tealSoft: "#DDF0EB",
  apricot: "#E0805C", apricotSoft: "#FBE9E0",
  board: "#1C5E54", boardDeep: "#12443D", chalk: "#F3EFE4",
  leaf: "#74A65C", leafSoft: "#E9F2E1",
  gold: "#D9A441", goldSoft: "#F9EED8",
};
const GRAIN =
  "url(\"data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.035 0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E\")";

const GRADE_POINTS = { A: 12, "A-": 11, "B+": 10, B: 9, "B-": 8, "C+": 7, C: 6, "C-": 5, "D+": 4, D: 3, "D-": 2, E: 1 };
const LADDER = ["E","D-","D","D+","C-","C","C+","B-","B","B+","A-","A"];
const pointsToGrade = (p) => LADDER[Math.min(11, Math.max(0, Math.round(p) - 1))];

// ---------- data (mastery-engine outputs, mocked) ----------
const SUBJECTS = [
  { name: "Mathematics", grade: "B-", t1: 6.5, elements: [
    { el: "Algebra", m: 82 }, { el: "Geometry", m: 54, ex: 12 }, { el: "Trigonometry", m: 61, ex: 9 },
    { el: "Probability & Statistics", m: 74 }, { el: "Calculus", m: 47, ex: 15 }, { el: "Commercial Arithmetic", m: 88 },
  ], fix: "Geometry and Calculus are holding you at B-. Clearing Calculus alone is worth one grade." },
  { name: "English", grade: "B+", t1: 9, elements: [
    { el: "Comprehension", m: 84 }, { el: "Grammar", m: 79 }, { el: "Composition", m: 71 }, { el: "Set Books", m: 66, ex: 8 },
  ], fix: "Set-book essays are your weakest paper. Practise two guided essays this week." },
  { name: "Kiswahili", grade: "B", t1: 8, elements: [
    { el: "Ufahamu", m: 80 }, { el: "Sarufi", m: 72 }, { el: "Insha", m: 63, ex: 10 }, { el: "Fasihi", m: 75 },
  ], fix: "Insha structure loses you marks. Focus on mwanzo–kati–mwisho drills." },
  { name: "Biology", grade: "C+", t1: 6, elements: [
    { el: "Cell Biology", m: 77 }, { el: "Genetics", m: 52, ex: 14 }, { el: "Ecology", m: 70 }, { el: "Physiology", m: 58, ex: 11 },
  ], fix: "Genetics crosses and physiology diagrams — both fixable with targeted practice." },
  { name: "Chemistry", grade: "C", t1: 6, elements: [
    { el: "The Mole", m: 58, ex: 13 }, { el: "Organic Chemistry", m: 41, ex: 18 }, { el: "Acids, Bases & Salts", m: 69 }, { el: "Electrochemistry", m: 55, ex: 9 },
  ], fix: "Organic Chemistry is your single weakest element across all subjects. Start here today." },
  { name: "Physics", grade: "B-", t1: 7, elements: [
    { el: "Mechanics", m: 76 }, { el: "Electricity", m: 64, ex: 7 }, { el: "Waves & Optics", m: 71 }, { el: "Modern Physics", m: 60, ex: 8 },
  ], fix: "Electricity numericals — practise circuit problems under time." },
  { name: "History & Govt", grade: "A-", t1: 10, elements: [
    { el: "Pre-Colonial Kenya", m: 90 }, { el: "Colonial Period", m: 85 }, { el: "Government & Constitution", m: 78 }, { el: "World History", m: 81 },
  ], fix: "You're on track for A-. Keep essay timing sharp to push for A." },
  { name: "Business Studies", grade: "B+", t1: 9, elements: [
    { el: "Trade & Commerce", m: 83 }, { el: "Bookkeeping", m: 68, ex: 6 }, { el: "Money & Banking", m: 80 }, { el: "Entrepreneurship", m: 86 },
  ], fix: "Bookkeeping ledger entries — small errors, easy marks to recover." },
];

const TREND = [
  { wk: "Term 1", pts: 7.4 }, { wk: "Mid-Yr", pts: 7.7 }, { wk: "Pre-Mock", pts: 8.1 }, { wk: "Mock 1", pts: 8.6 }, { wk: "Now", pts: 9.0 },
];
const STUDY_WEEKS = [
  { wk: "W1", h: 14 }, { wk: "W2", h: 18 }, { wk: "W3", h: 16 }, { wk: "W4", h: 21 },
  { wk: "W5", h: 19 }, { wk: "W6", h: 23 }, { wk: "W7", h: 20 }, { wk: "W8", h: 24 },
];
const MOCK1 = [
  { s: "Mathematics", g: "C+", now: "B-" }, { s: "English", g: "B+", now: "B+" }, { s: "Kiswahili", g: "B-", now: "B" },
  { s: "Biology", g: "C", now: "C+" }, { s: "Chemistry", g: "C-", now: "C" }, { s: "Physics", g: "C+", now: "B-" },
  { s: "History & Govt", g: "B+", now: "A-" }, { s: "Business Studies", g: "B", now: "B+" },
];

const QUESTIONS = [
  { subject: "Mathematics", els: ["Calculus"], q: "The gradient of the curve y = 2x³ − 3x² + 5 at x = 2 is:",
    opts: ["12", "18", "24", "6"], ans: 0,
    why: "dy/dx = 6x² − 6x. At x = 2: 6(4) − 6(2) = 24 − 12 = 12. Differentiate first, then substitute — don't substitute into y." },
  { subject: "Mathematics", els: ["Geometry"], q: "The interior angle of a regular polygon is 150°. How many sides does it have?",
    opts: ["10", "12", "15", "9"], ans: 1,
    why: "Exterior angle = 180° − 150° = 30°. Sides = 360° ÷ 30° = 12. Working through the exterior angle is always the shortcut." },
  { subject: "Chemistry", els: ["Organic Chemistry"], q: "Which reagent distinguishes an alkene from an alkane?",
    opts: ["Sodium hydroxide", "Bromine water", "Litmus paper", "Silver nitrate"], ans: 1,
    why: "Bromine water decolourises in an alkene (addition across C=C) but stays orange with an alkane. A classic KCSE Paper 1 mark." },
  { subject: "Chemistry", els: ["The Mole"], q: "How many moles are in 8.8 g of CO₂? (C=12, O=16)",
    opts: ["0.1", "0.2", "0.4", "0.8"], ans: 1,
    why: "Molar mass of CO₂ = 12 + 32 = 44 g/mol. Moles = 8.8 ÷ 44 = 0.2. Always assemble molar mass before dividing." },
  { subject: "Biology", els: ["Genetics"], q: "In a monohybrid cross of two heterozygous parents (Tt × Tt), the phenotype ratio is:",
    opts: ["1:1", "1:2:1", "3:1", "9:3:3:1"], ans: 2,
    why: "Tt × Tt gives TT, Tt, Tt, tt — three showing the dominant trait to one recessive. 1:2:1 is the *genotype* ratio; the question asked phenotype." },
];

const daysToKCSE = () => Math.max(0, Math.ceil((new Date("2026-11-02T08:00:00+03:00") - new Date()) / 86400000));
const readiness = (s) => Math.round(s.elements.reduce((a, e) => a + e.m, 0) / s.elements.length);
const NAV = ["Dashboard", "My Subjects", "Practise", "Mock Exams", "Progress"];

// ---------- shared bits ----------
const Eyebrow = ({ children, style }) => <div className="eyebrow" style={style}>{children}</div>;

function ElementRows({ elements, bar, onPractise }) {
  return (
    <div className="space-y-3">
      {elements.map((e) => {
        const w = e.m < 65;
        return (
          <div key={e.el} className="flex items-center gap-3">
            <span className={`text-sm flex-1 min-w-0 truncate ${w ? "weakname" : ""}`}>{e.el}</span>
            <span className="flex-1 max-w-40" style={{ height: 8, background: C.rule, borderRadius: 999 }}>
              <span className="block h-full" style={{ ...bar(e.m), background: w ? C.apricot : e.m >= 75 ? C.leaf : C.teal, borderRadius: 999 }} />
            </span>
            <span className="mono text-xs font-semibold" style={{ width: 34, textAlign: "right" }}>{e.m}%</span>
            {w ? (
              <button className="practise mono px-3 py-1.5 shrink-0" style={{ fontSize: 11, background: C.apricot, color: "#fff", border: "none", borderRadius: 999, cursor: "pointer" }}
                onClick={() => onPractise && onPractise(e.el)}>
                Practise · {e.ex}
              </button>
            ) : <span style={{ width: 86 }} aria-hidden="true" />}
          </div>
        );
      })}
    </div>
  );
}

// ---------- pages ----------
function Dashboard({ bar, meanPts, meanGrade, target, plan, setPlan, recs, go }) {
  const [open, setOpen] = useState(0);
  const doneCount = plan.filter((p) => p.done).length;
  return (
    <>
      <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mt-6">
        <div className="card p-4">
          <Eyebrow>Predicted mean grade</Eyebrow>
          <div className="flex items-baseline gap-2 mt-1">
            <span className="display" style={{ fontSize: "2.6rem", lineHeight: 1, color: C.teal, textShadow: `2px 2px 0 ${C.tealSoft}` }}>{meanGrade}</span>
            <span className="mono text-sm" style={{ opacity: 0.6 }}>{meanPts.toFixed(1)}/12</span>
          </div>
          <div className="flex gap-0.5 mt-3" aria-hidden="true">
            {LADDER.map((g) => (
              <div key={g} className="flex-1" style={{ height: 5, borderRadius: 99, background: GRADE_POINTS[g] <= meanPts ? C.teal : C.rule, boxShadow: g === target ? `0 0 0 1.5px ${C.gold}` : "none" }} />
            ))}
          </div>
          <div className="mono mt-1.5" style={{ fontSize: 10, color: C.leaf }}>▲ +0.4 pts this month</div>
        </div>
        <div className="card p-4 flex items-center gap-3">
          <div className="mono text-center shrink-0" style={{ width: 62, height: 62, borderRadius: "50%", border: `2px solid ${C.gold}`, boxShadow: `inset 0 0 0 2px #fff, inset 0 0 0 3.5px ${C.gold}`, color: C.gold, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", transform: "rotate(-6deg)" }}>
            <span style={{ fontSize: 7.5, letterSpacing: "0.15em" }}>TARGET</span>
            <span className="display" style={{ fontSize: 20, lineHeight: 1 }}>{target}</span>
          </div>
          <div>
            <Eyebrow>Your target</Eyebrow>
            <div className="text-sm mt-1" style={{ opacity: 0.75 }}>1 point away.<br />Within reach.</div>
          </div>
        </div>
        <div className="card p-4">
          <Eyebrow>Days to Paper 1</Eyebrow>
          <div className="display mt-1" style={{ fontSize: "2.6rem", lineHeight: 1 }}>{daysToKCSE()}</div>
          <div className="mono mt-2" style={{ fontSize: 10, opacity: 0.55 }}>KCSE starts 2 Nov 2026</div>
        </div>
        <div className="card p-4">
          <Eyebrow>Study streak</Eyebrow>
          <div className="flex items-baseline gap-1 mt-1">
            <span className="display" style={{ fontSize: "2.6rem", lineHeight: 1, color: C.leaf }}>12</span>
            <span className="text-sm" style={{ opacity: 0.6 }}>days</span>
          </div>
          <div className="flex gap-1 mt-2">
            {["M","T","W","T","F","S","S"].map((d, i) => (
              <span key={i} className="mono text-center" style={{ width: 20, height: 20, lineHeight: "19px", fontSize: 9, borderRadius: "50%", background: i < 4 ? C.leafSoft : "transparent", border: `1px solid ${i < 4 ? C.leaf : C.rule}`, color: i < 4 ? C.leaf : "rgba(51,57,62,.4)" }}>{d}</span>
            ))}
          </div>
        </div>
      </div>

      <div className="grid lg:grid-cols-3 gap-5 mt-5 items-start">
        <div className="lg:col-span-2 space-y-5 min-w-0">
          <section className="card p-5">
            <div className="flex items-baseline justify-between">
              <h2 className="display text-base">Your subjects</h2>
              <span className="eyebrow">best 7 of 8 count · tap to open</span>
            </div>
            <div className="mt-3">
              {SUBJECTS.map((sub, i) => {
                const weak = sub.elements.filter((e) => e.m < 65);
                const ready = readiness(sub);
                const isOpen = open === i;
                return (
                  <div key={sub.name} style={{ borderBottom: i < SUBJECTS.length - 1 ? `1px solid ${C.rule}` : "none" }}>
                    <button onClick={() => setOpen(isOpen ? -1 : i)} aria-expanded={isOpen}
                      className="subrow w-full text-left flex items-center gap-3 py-2.5 px-2"
                      style={{ background: "transparent", border: "none", cursor: "pointer", color: C.ink, borderRadius: 10 }}>
                      <span className="display text-center shrink-0" style={{ width: 38, height: 28, lineHeight: "26px", fontSize: 13.5, borderRadius: 999, color: isOpen ? "#fff" : C.teal, background: isOpen ? C.teal : C.tealSoft }}>{sub.grade}</span>
                      <span className="text-sm font-semibold truncate" style={{ minWidth: 104, color: isOpen ? C.teal : C.ink }}>{sub.name}</span>
                      <span className="flex-1 hidden sm:block" style={{ height: 6, background: C.rule, borderRadius: 999 }}>
                        <span className="block h-full" style={{ width: `${ready}%`, background: ready >= 75 ? C.leaf : ready >= 60 ? C.teal : C.apricot, borderRadius: 999 }} />
                      </span>
                      <span className="mono text-xs font-semibold" style={{ width: 34, textAlign: "right" }}>{ready}%</span>
                      <span className="mono px-2 py-0.5 shrink-0" style={{ fontSize: 10.5, borderRadius: 999, color: weak.length ? C.apricot : C.leaf, background: weak.length ? C.apricotSoft : C.leafSoft }}>
                        {weak.length ? `${weak.length} to fix` : "on track"}
                      </span>
                      <span aria-hidden="true" className="mono text-xs" style={{ opacity: 0.4, transform: isOpen ? "rotate(90deg)" : "none", transition: "transform 200ms" }}>›</span>
                    </button>
                    {isOpen && (
                      <div className="expand pb-4 px-2">
                        <p className="text-sm mt-1 mb-3 pl-3" style={{ borderLeft: `3px solid ${C.apricot}`, opacity: 0.85, fontStyle: "italic" }}>{sub.fix}</p>
                        <ElementRows elements={sub.elements} bar={bar} onPractise={() => go("Practise")} />
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </section>
        </div>

        <div className="space-y-5 min-w-0">
          <section className="card p-5">
            <div className="flex items-baseline justify-between">
              <h2 className="display text-base">Today's plan</h2>
              <span className="mono text-xs" style={{ color: C.leaf }}>{doneCount}/{plan.length}</span>
            </div>
            <div className="mt-1" style={{ height: 5, background: C.rule, borderRadius: 999 }}>
              <div className="h-full" style={{ ...bar((doneCount / plan.length) * 100), background: C.leaf, borderRadius: 999 }} />
            </div>
            <div className="mt-3 space-y-1">
              {plan.map((p, i) => (
                <button key={p.t} onClick={() => setPlan(plan.map((x, j) => j === i ? { ...x, done: !x.done } : x))}
                  className="w-full flex items-center gap-2.5 py-1.5 text-left" style={{ background: "transparent", border: "none", cursor: "pointer", color: C.ink }}>
                  <span aria-hidden="true" className="mono text-center shrink-0" style={{ width: 18, height: 18, lineHeight: "16px", fontSize: 11, borderRadius: 6, border: `1.5px solid ${p.done ? C.leaf : C.rule}`, background: p.done ? C.leaf : "transparent", color: "#fff" }}>{p.done ? "✓" : ""}</span>
                  <span className="text-sm flex-1 min-w-0 truncate" style={{ textDecoration: p.done ? "line-through" : "none", opacity: p.done ? 0.5 : 1 }}>{p.t}</span>
                  <span className="mono text-xs shrink-0" style={{ opacity: 0.5 }}>{p.h}</span>
                </button>
              ))}
            </div>
          </section>

          <section className="card p-5">
            <h2 className="display text-base">Start here</h2>
            <p className="text-xs mt-1" style={{ opacity: 0.6 }}>Your three weakest elements right now.</p>
            <div className="mt-3 space-y-2.5">
              {recs.map((r) => (
                <button key={r.subject + r.el} onClick={() => go("Practise")} className="practise w-full text-left px-3 py-2.5 flex items-center gap-3"
                  style={{ background: C.apricotSoft, border: `1px solid ${C.apricot}33`, borderRadius: 12, cursor: "pointer", color: C.ink }}>
                  <span className="mono shrink-0 text-center" style={{ width: 38, fontSize: 12, fontWeight: 600, color: C.apricot }}>{r.m}%</span>
                  <span className="min-w-0">
                    <span className="block text-sm font-semibold truncate">{r.el}</span>
                    <span className="mono block" style={{ fontSize: 10, opacity: 0.6 }}>{r.subject} · {r.ex} exercises</span>
                  </span>
                  <span className="mono text-xs ml-auto shrink-0" style={{ color: C.apricot }}>→</span>
                </button>
              ))}
            </div>
          </section>
        </div>
      </div>
    </>
  );
}

function SubjectsPage({ bar, go }) {
  const [sel, setSel] = useState(null);
  const s = sel !== null ? SUBJECTS[sel] : null;
  return (
    <>
      <div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mt-6">
        {SUBJECTS.map((sub, i) => {
          const ready = readiness(sub);
          const weak = sub.elements.filter((e) => e.m < 65).length;
          const active = sel === i;
          return (
            <button key={sub.name} onClick={() => setSel(active ? null : i)} aria-pressed={active}
              className="card p-4 text-left" style={{ cursor: "pointer", outline: active ? `2px solid ${C.teal}` : "none", color: C.ink }}>
              <div className="flex items-center justify-between">
                <span className="display" style={{ fontSize: "1.8rem", color: C.teal }}>{sub.grade}</span>
                <span className="mono" style={{ fontSize: 10, color: ready >= 75 ? C.leaf : ready >= 60 ? C.teal : C.apricot }}>{ready}%</span>
              </div>
              <div className="text-sm font-semibold mt-1 truncate">{sub.name}</div>
              <div className="mt-2" style={{ height: 5, background: C.rule, borderRadius: 999 }}>
                <div className="h-full" style={{ ...bar(ready), background: ready >= 75 ? C.leaf : ready >= 60 ? C.teal : C.apricot, borderRadius: 999 }} />
              </div>
              <div className="mono mt-2" style={{ fontSize: 10, color: weak ? C.apricot : C.leaf }}>{weak ? `${weak} elements to fix` : "on track"}</div>
            </button>
          );
        })}
      </div>
      {s && (
        <section className="card p-5 mt-5 expand">
          <div className="flex items-baseline justify-between flex-wrap gap-2">
            <h2 className="display text-base">{s.name}</h2>
            <span className="mono text-sm px-2.5 py-1" style={{ color: C.teal, background: C.tealSoft, borderRadius: 999 }}>predicted {s.grade}</span>
          </div>
          <p className="text-sm mt-3 pl-3" style={{ borderLeft: `3px solid ${C.apricot}`, opacity: 0.85, fontStyle: "italic" }}>{s.fix}</p>
          <div className="mt-4">
            <ElementRows elements={s.elements} bar={bar} onPractise={() => go("Practise")} />
          </div>
        </section>
      )}
      {!s && <p className="mono text-xs mt-5" style={{ opacity: 0.5 }}>Tap a subject to see its element breakdown.</p>}
    </>
  );
}

function PractisePage({ recs }) {
  const [filter, setFilter] = useState(null);
  const pool = useMemo(() => filter ? QUESTIONS.filter((q) => q.els.includes(filter)) : QUESTIONS, [filter]);
  const [idx, setIdx] = useState(0);
  const [picked, setPicked] = useState(null);
  const [score, setScore] = useState({ right: 0, total: 0 });
  const q = pool[idx % pool.length];
  const answered = picked !== null;
  const correct = answered && picked === q.ans;

  const choose = (i) => {
    if (answered) return;
    setPicked(i);
    setScore((s) => ({ right: s.right + (i === q.ans ? 1 : 0), total: s.total + 1 }));
  };
  const next = () => { setPicked(null); setIdx((i) => i + 1); };
  const pickFilter = (el) => { setFilter(el === filter ? null : el); setIdx(0); setPicked(null); };

  return (
    <>
      <div className="flex items-center gap-2 flex-wrap mt-6">
        <span className="eyebrow">Focus on:</span>
        {recs.map((r) => (
          <button key={r.el} onClick={() => pickFilter(r.el)}
            className="mono px-3 py-1.5" style={{ fontSize: 11, borderRadius: 999, cursor: "pointer", border: `1.5px solid ${filter === r.el ? C.apricot : C.rule}`, background: filter === r.el ? C.apricot : C.card, color: filter === r.el ? "#fff" : C.ink }}>
            {r.el} · {r.m}%
          </button>
        ))}
        {filter && <button onClick={() => pickFilter(filter)} className="mono text-xs" style={{ background: "none", border: "none", cursor: "pointer", color: C.teal }}>clear</button>}
        <span className="mono text-xs ml-auto" style={{ opacity: 0.6 }}>Session: {score.right}/{score.total}</span>
      </div>

      <section className="card p-6 mt-4 max-w-2xl">
        <div className="flex items-center justify-between">
          <Eyebrow>{q.subject} · {q.els.join(", ")}</Eyebrow>
          <span className="mono text-xs" style={{ opacity: 0.45 }}>Q{(idx % pool.length) + 1} of {pool.length}</span>
        </div>
        <h2 className="text-lg font-semibold mt-3" style={{ lineHeight: 1.45 }}>{q.q}</h2>
        <div className="mt-4 space-y-2">
          {q.opts.map((o, i) => {
            const isPick = picked === i, isAns = i === q.ans;
            let bg = C.card, bd = C.rule, col = C.ink;
            if (answered && isAns) { bg = C.leafSoft; bd = C.leaf; col = C.ink; }
            else if (answered && isPick && !isAns) { bg = C.apricotSoft; bd = C.apricot; }
            return (
              <button key={o} onClick={() => choose(i)} disabled={answered && !isPick && !isAns}
                className="w-full text-left px-4 py-3 flex items-center gap-3"
                style={{ background: bg, border: `1.5px solid ${bd}`, borderRadius: 12, cursor: answered ? "default" : "pointer", color: col, transition: "background 150ms, border 150ms" }}>
                <span className="mono text-xs" style={{ width: 18, opacity: 0.5 }}>{String.fromCharCode(65 + i)}</span>
                <span className="text-sm">{o}</span>
                {answered && isAns && <span className="mono text-xs ml-auto" style={{ color: C.leaf }}>✓ correct</span>}
                {answered && isPick && !isAns && <span className="mono text-xs ml-auto" style={{ color: C.apricot }}>your pick</span>}
              </button>
            );
          })}
        </div>

        {answered && (
          <div className="expand mt-4 px-4 py-3" style={{ background: correct ? C.leafSoft : C.apricotSoft, borderRadius: 12 }}>
            <div className="mono text-xs font-semibold" style={{ color: correct ? C.leaf : C.apricot }}>
              {correct ? "Correct — mastery nudged up." : "Not this time — mastery noted."}
            </div>
            <p className="text-sm mt-1.5" style={{ lineHeight: 1.55 }}>{q.why}</p>
            <div className="flex items-center gap-2 mt-3 flex-wrap">
              {q.els.map((el) => (
                <span key={el} className="mono px-2 py-0.5" style={{ fontSize: 10.5, borderRadius: 999, background: "#fff", border: `1px solid ${C.rule}` }}>
                  {el} {correct ? "▲ +2" : "▼ −1"}
                </span>
              ))}
              <button onClick={next} className="practise mono text-xs px-4 py-2 ml-auto" style={{ background: C.teal, color: "#fff", border: "none", borderRadius: 999, cursor: "pointer" }}>
                Next question →
              </button>
            </div>
          </div>
        )}
      </section>
      <p className="mono text-xs mt-4" style={{ opacity: 0.5 }}>Every answer updates your element mastery — your dashboard reads from the same scores.</p>
    </>
  );
}

function MocksPage() {
  return (
    <>
      <div className="grid lg:grid-cols-3 gap-5 mt-6 items-start">
        <section className="card p-5" style={{ borderColor: C.gold }}>
          <Eyebrow style={{ color: C.gold, opacity: 1 }}>Next up</Eyebrow>
          <h2 className="display text-lg mt-1">Mock Exam 2</h2>
          <div className="mono text-sm mt-1" style={{ opacity: 0.65 }}>25 – 30 July · school hall</div>
          <div className="display mt-3" style={{ fontSize: "2.4rem", color: C.gold }}>3 <span className="text-base" style={{ color: C.ink, opacity: 0.6 }}>days to go</span></div>
          <button className="practise mono text-xs px-4 py-2.5 mt-4 w-full" style={{ background: C.teal, color: "#fff", border: "none", borderRadius: 999, cursor: "pointer" }}>
            Take a 1-hour practice mock
          </button>
          <p className="text-xs mt-3" style={{ opacity: 0.6 }}>A timed mini-mock sharpens exam pace — your weakest elements are weighted in.</p>
        </section>

        <section className="card p-5 lg:col-span-2">
          <div className="flex items-baseline justify-between">
            <h2 className="display text-base">Mock 1 (July) vs where you are now</h2>
            <span className="mono text-xs" style={{ opacity: 0.55 }}>mean: C+ → B</span>
          </div>
          <div className="mt-3">
            <div className="grid grid-cols-12 gap-2 py-1.5 mono" style={{ fontSize: 10.5, opacity: 0.55 }}>
              <span className="col-span-6">Subject</span><span className="col-span-2 text-center">Mock 1</span>
              <span className="col-span-2 text-center">Now</span><span className="col-span-2 text-center">Move</span>
            </div>
            {MOCK1.map((r, i) => {
              const d = GRADE_POINTS[r.now] - GRADE_POINTS[r.g];
              return (
                <div key={r.s} className="grid grid-cols-12 gap-2 items-center py-2" style={{ borderTop: `1px solid ${C.rule}` }}>
                  <span className="col-span-6 text-sm font-semibold truncate">{r.s}</span>
                  <span className="col-span-2 mono text-sm text-center" style={{ opacity: 0.6 }}>{r.g}</span>
                  <span className="col-span-2 mono text-sm text-center font-semibold" style={{ color: C.teal }}>{r.now}</span>
                  <span className="col-span-2 mono text-xs text-center" style={{ color: d > 0 ? C.leaf : d < 0 ? C.apricot : "rgba(51,57,62,.4)" }}>
                    {d > 0 ? `▲ +${d}` : d < 0 ? `▼ ${d}` : "—"}
                  </span>
                </div>
              );
            })}
          </div>
          <p className="mono text-xs mt-3" style={{ opacity: 0.5 }}>“Now” is your live predicted grade from element mastery — mock results recalibrate it.</p>
        </section>
      </div>
    </>
  );
}

function ProgressPage({ meanGrade, target }) {
  return (
    <>
      <div className="grid lg:grid-cols-2 gap-5 mt-6 items-start">
        <section className="card p-5">
          <div className="flex items-baseline justify-between">
            <h2 className="display text-base">Your climb</h2>
            <span className="eyebrow">mean points per checkpoint</span>
          </div>
          <div style={{ height: 200 }} className="mt-3">
            <ResponsiveContainer width="100%" height="100%">
              <AreaChart data={TREND} margin={{ top: 8, right: 10, bottom: 0, left: -16 }}>
                <defs>
                  <linearGradient id="tealFade" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stopColor={C.teal} stopOpacity={0.2} />
                    <stop offset="100%" stopColor={C.teal} stopOpacity={0} />
                  </linearGradient>
                </defs>
                <XAxis dataKey="wk" tick={{ fontFamily: "IBM Plex Mono", fontSize: 11, fill: "rgba(51,57,62,.6)" }} stroke={C.rule} tickLine={false} />
                <YAxis domain={[6, 12]} tick={{ fontFamily: "IBM Plex Mono", fontSize: 11, fill: "rgba(51,57,62,.6)" }} stroke="transparent" tickLine={false} />
                <Tooltip formatter={(v) => [`${v} pts (${pointsToGrade(v)})`, "Mean"]} contentStyle={{ borderRadius: 10, border: `1px solid ${C.rule}`, fontFamily: "IBM Plex Sans" }} />
                <ReferenceLine y={GRADE_POINTS[target]} stroke={C.gold} strokeDasharray="5 4"
                  label={{ value: `Target ${target}`, fill: C.gold, fontSize: 11, fontFamily: "IBM Plex Mono", position: "insideTopRight" }} />
                <Area type="monotone" dataKey="pts" stroke={C.teal} strokeWidth={2.5} fill="url(#tealFade)" dot={{ r: 3.5, fill: C.teal, strokeWidth: 0 }} activeDot={{ r: 5 }} />
              </AreaChart>
            </ResponsiveContainer>
          </div>
        </section>

        <section className="card p-5">
          <div className="flex items-baseline justify-between">
            <h2 className="display text-base">Hours in the books</h2>
            <span className="eyebrow">last 8 weeks</span>
          </div>
          <div style={{ height: 200 }} className="mt-3">
            <ResponsiveContainer width="100%" height="100%">
              <BarChart data={STUDY_WEEKS} margin={{ top: 8, right: 10, bottom: 0, left: -22 }}>
                <XAxis dataKey="wk" tick={{ fontFamily: "IBM Plex Mono", fontSize: 11, fill: "rgba(51,57,62,.6)" }} stroke={C.rule} tickLine={false} />
                <YAxis tick={{ fontFamily: "IBM Plex Mono", fontSize: 11, fill: "rgba(51,57,62,.6)" }} stroke="transparent" tickLine={false} />
                <Tooltip formatter={(v) => [`${v} h`, "Study time"]} contentStyle={{ borderRadius: 10, border: `1px solid ${C.rule}`, fontFamily: "IBM Plex Sans" }} />
                <Bar dataKey="h" fill={C.leaf} radius={[6, 6, 0, 0]} />
              </BarChart>
            </ResponsiveContainer>
          </div>
        </section>
      </div>

      <section className="card p-5 mt-5">
        <h2 className="display text-base">Grade movement since Term 1</h2>
        <div className="mt-3 grid sm:grid-cols-2 gap-x-8">
          {SUBJECTS.map((s) => {
            const d = GRADE_POINTS[s.grade] - s.t1;
            return (
              <div key={s.name} className="flex items-center gap-3 py-2" style={{ borderBottom: `1px solid ${C.rule}` }}>
                <span className="text-sm font-semibold flex-1 min-w-0 truncate">{s.name}</span>
                <span className="mono text-sm" style={{ opacity: 0.55 }}>{pointsToGrade(s.t1)}</span>
                <span className="mono text-xs" style={{ opacity: 0.4 }}>→</span>
                <span className="mono text-sm font-semibold" style={{ color: C.teal }}>{s.grade}</span>
                <span className="mono text-xs" style={{ width: 44, textAlign: "right", color: d > 0 ? C.leaf : d < 0 ? C.apricot : "rgba(51,57,62,.4)" }}>
                  {d > 0 ? `▲ +${d}` : d < 0 ? `▼ ${d}` : "—"}
                </span>
              </div>
            );
          })}
        </div>
        <p className="mono text-xs mt-3" style={{ opacity: 0.5 }}>Now at {meanGrade} overall — predictions are estimates, not KNEC results.</p>
      </section>
    </>
  );
}

// ---------- app shell ----------
export default function TayariApp() {
  const [page, setPage] = useState("Dashboard");
  const [plan, setPlan] = useState([
    { t: "Mathematics — Calculus drill", h: "1.5h", done: true },
    { t: "Chemistry — Organic reactions", h: "1.5h", done: true },
    { t: "English — Set-book essay", h: "1h", done: false },
    { t: "Kiswahili — Insha practice", h: "1h", done: false },
    { t: "Biology — Genetics crosses", h: "1h", done: false },
  ]);
  const [mounted, setMounted] = useState(false);
  useEffect(() => { const t = setTimeout(() => setMounted(true), 60); return () => clearTimeout(t); }, []);
  const bar = (w) => ({ width: mounted ? `${w}%` : "0%", transition: "width 900ms cubic-bezier(.22,1,.36,1)" });

  const meanPts = useMemo(() => {
    const pts = SUBJECTS.map((x) => GRADE_POINTS[x.grade]).sort((a, b) => b - a).slice(0, 7);
    return pts.reduce((a, b) => a + b, 0) / 7;
  }, []);
  const meanGrade = pointsToGrade(meanPts);
  const target = "B+";
  const recs = useMemo(() =>
    SUBJECTS.flatMap((s) => s.elements.filter((e) => e.m < 65).map((e) => ({ ...e, subject: s.name })))
      .sort((a, b) => a.m - b.m).slice(0, 3), []);

  const greeting = { Dashboard: "Habari ya asubuhi, Wanjiru", "My Subjects": "Your eight subjects", Practise: "Practise", "Mock Exams": "Mock exams", Progress: "Your progress" }[page];
  const sub = { Dashboard: "Every hour you put in now is a mark in November.", "My Subjects": "Tap any subject to see exactly what to fix.", Practise: "Short, targeted sets beat long, tired nights.", "Mock Exams": "Mocks calibrate your prediction — treat them like the real thing.", Progress: "Look how far you've come since Term 1." }[page];

  return (
    <div style={{ background: `${GRAIN}, linear-gradient(${C.paper}, ${C.paperDeep})`, color: C.ink, minHeight: "100vh", fontFamily: "'IBM Plex Sans', system-ui, sans-serif" }}>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,600;1,400&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
        .mono { font-family: 'IBM Plex Mono', ui-monospace, monospace; }
        .display { font-family: 'Archivo Black', 'IBM Plex Sans', sans-serif; letter-spacing: -0.01em; }
        .eyebrow { font-family: 'IBM Plex Mono', monospace; font-size: 10.5px; letter-spacing: 0.2em; text-transform: uppercase; opacity: 0.55; }
        .card { background: ${C.card}; border: 1px solid ${C.rule}; border-radius: 14px; box-shadow: 0 1px 2px rgba(51,57,62,.05), 0 8px 24px rgba(51,57,62,.06); }
        .weakname { text-decoration: underline wavy ${C.apricot}; text-decoration-thickness: 1.5px; text-underline-offset: 4px; }
        .subrow { transition: background 150ms ease; }
        .subrow:hover { background: rgba(46,139,122,0.05); }
        .practise { transition: background 140ms ease, transform 140ms ease, filter 140ms ease; }
        .practise:hover { filter: brightness(0.92); transform: translateY(-1px); }
        .navbtn { transition: background 140ms ease; border-radius: 10px; }
        .navbtn:hover { background: rgba(243,239,228,0.12); }
        .expand { animation: openUp 320ms ease both; }
        @keyframes openUp { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
        button:focus-visible { outline: 3px solid ${C.teal}; outline-offset: 2px; }
        @media (prefers-reduced-motion: reduce) { .expand { animation: none; } * { transition: none !important; } }
      `}</style>

      <div className="flex min-h-screen">
        <aside className="hidden lg:flex flex-col w-56 shrink-0 px-4 py-6"
          style={{ background: `${GRAIN}, linear-gradient(170deg, ${C.board}, ${C.boardDeep})`, color: C.chalk }}>
          <div className="px-2">
            <div className="display text-lg" style={{ color: "#fff" }}>Tayari</div>
            <div className="eyebrow mt-1" style={{ color: C.gold, opacity: 1 }}>Uko tayari?</div>
          </div>
          <nav className="mt-8 space-y-1" aria-label="Main">
            {NAV.map((n) => (
              <button key={n} onClick={() => setPage(n)} aria-current={page === n ? "page" : undefined}
                className="navbtn w-full text-left px-3 py-2 text-sm"
                style={{ background: page === n ? "rgba(217,164,65,0.2)" : "transparent", color: page === n ? "#fff" : "rgba(243,239,228,.75)", border: "none", cursor: "pointer", fontWeight: page === n ? 600 : 400 }}>
                {n}
              </button>
            ))}
          </nav>
          <div className="mt-auto">
            <div className="px-3 py-3 text-xs" style={{ background: "rgba(243,239,228,0.08)", borderRadius: 12, color: "rgba(243,239,228,.85)", fontStyle: "italic" }}>
              Small daily progress, big December results.
            </div>
            <div className="flex items-center gap-2 mt-4 px-2">
              <div className="display" style={{ width: 34, height: 34, borderRadius: "50%", background: C.gold, color: C.boardDeep, display: "grid", placeItems: "center", fontSize: 13 }}>W</div>
              <div>
                <div className="text-sm font-semibold" style={{ color: "#fff" }}>Wanjiru M.</div>
                <div className="mono" style={{ fontSize: 10, opacity: 0.6 }}>Form 4 · 2026</div>
              </div>
            </div>
          </div>
        </aside>

        <div className="flex-1 min-w-0 px-5 lg:px-8 py-6 max-w-6xl mx-auto">
          {/* mobile nav */}
          <div className="lg:hidden flex gap-2 overflow-x-auto pb-3 -mx-1 px-1">
            {NAV.map((n) => (
              <button key={n} onClick={() => setPage(n)}
                className="mono shrink-0 px-3 py-1.5" style={{ fontSize: 11, borderRadius: 999, cursor: "pointer", border: `1.5px solid ${page === n ? C.teal : C.rule}`, background: page === n ? C.teal : C.card, color: page === n ? "#fff" : C.ink }}>
                {n}
              </button>
            ))}
          </div>

          <div className="flex items-end justify-between flex-wrap gap-3">
            <div>
              <h1 className="display text-2xl">{greeting}</h1>
              <p className="text-sm mt-1" style={{ opacity: 0.7 }}>{sub}</p>
            </div>
            <div className="mono text-xs px-3 py-1.5" style={{ background: C.card, border: `1px solid ${C.rule}`, borderRadius: 999 }}>Wed 22 July 2026 · Term 2</div>
          </div>

          {page === "Dashboard" && <Dashboard bar={bar} meanPts={meanPts} meanGrade={meanGrade} target={target} plan={plan} setPlan={setPlan} recs={recs} go={setPage} />}
          {page === "My Subjects" && <SubjectsPage bar={bar} go={setPage} />}
          {page === "Practise" && <PractisePage recs={recs} />}
          {page === "Mock Exams" && <MocksPage />}
          {page === "Progress" && <ProgressPage meanGrade={meanGrade} target={target} />}
        </div>
      </div>
    </div>
  );
}

Youez - 2016 - github.com/yon3zu
LinuXploit