===== EasyGrader.tsx =====
import { useState, useMemo } from "react";

function getGradeColor(pct: number): string {
  if (pct >= 90) return "bg-[#1a1a1a]";
  if (pct >= 80) return "bg-[#8B0000]";
  if (pct >= 70) return "bg-[#A00000]";
  if (pct >= 60) return "bg-[#B52020]";
  if (pct >= 50) return "bg-[#C43030]";
  if (pct >= 40) return "bg-[#CF3f3f]";
  if (pct >= 30) return "bg-[#D95050]";
  if (pct >= 20) return "bg-[#E06060]";
  if (pct >= 10) return "bg-[#E87070]";
  return "bg-[#F08080]";
}

export function EasyGrader() {
  const [numQuestions, setNumQuestions] = useState("10");
  const [numWrong, setNumWrong] = useState("0");
  const [showChart, setShowChart] = useState(true);
  const [showDecimals, setShowDecimals] = useState(false);

  const result = useMemo(() => {
    const total = parseInt(numQuestions) || 0;
    const wrong = parseInt(numWrong) || 0;
    if (total <= 0) return null;
    const correct = Math.max(0, total - wrong);
    const pct = (correct / total) * 100;
    return { correct, total, pct };
  }, [numQuestions, numWrong]);

  const chartRows = useMemo(() => {
    const total = parseInt(numQuestions) || 0;
    if (total <= 0) return [];
    const rows = [];
    for (let w = 1; w <= total; w++) {
      const correct = total - w;
      const pct = (correct / total) * 100;
      rows.push({ wrong: w, pct });
    }
    return rows;
  }, [numQuestions]);

  const formatPct = (pct: number) => {
    if (showDecimals) return pct.toFixed(2) + "%";
    return Math.round(pct) + "%";
  };

  return (
    <div className="max-w-2xl mx-auto space-y-6">
      {/* Inputs */}
      <div className="bg-[#f0f0f0] rounded-md px-6 py-5 flex flex-wrap gap-6 items-center">
        <div className="flex items-center gap-3">
          <label className="text-sm font-medium text-gray-700 whitespace-nowrap">
            # of questions:
          </label>
          <input
            type="number"
            min="1"
            value={numQuestions}
            onChange={(e) => setNumQuestions(e.target.value)}
            className="w-24 border border-gray-300 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 bg-white"
          />
        </div>
        <div className="flex items-center gap-3">
          <label className="text-sm font-medium text-gray-700 whitespace-nowrap">
            # wrong:
          </label>
          <input
            type="number"
            min="0"
            value={numWrong}
            onChange={(e) => setNumWrong(e.target.value)}
            className="w-24 border border-gray-300 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-gray-400 bg-white"
          />
        </div>
      </div>

      {/* Result */}
      <div className="bg-[#f8f8f8] rounded-md px-6 py-5 space-y-4">
        <div className="text-sm font-medium text-gray-600 text-center">Result</div>
        <div className="border border-gray-300 rounded bg-white px-6 py-3 text-center text-2xl font-semibold text-gray-800">
          {result
            ? `${result.correct}/${result.total} = ${formatPct(result.pct)}`
            : "—"}
        </div>

        {/* Options */}
        <div className="flex flex-wrap gap-6 justify-center text-sm text-gray-700">
          <label className="flex items-center gap-2 cursor-pointer select-none">
            <input
              type="checkbox"
              checked={showChart}
              onChange={(e) => setShowChart(e.target.checked)}
              className="w-4 h-4 accent-gray-700"
            />
            Show Grading Chart
          </label>
          <label className="flex items-center gap-2 cursor-pointer select-none">
            <input
              type="checkbox"
              checked={showDecimals}
              onChange={(e) => setShowDecimals(e.target.checked)}
              className="w-4 h-4 accent-gray-700"
            />
            Show Decimals
          </label>
        </div>

        {/* Grading Chart */}
        {showChart && chartRows.length > 0 && (
          <div className="mt-2">
            <div className="text-sm font-medium text-gray-700 mb-2 text-center">Grading Chart:</div>
            <table className="w-full text-sm">
              <thead>
                <tr className="text-gray-700">
                  <th className="py-1.5 font-semibold text-center w-1/2"># Wrong</th>
                  <th className="py-1.5 font-semibold text-center w-1/2">Grade</th>
                </tr>
              </thead>
              <tbody>
                {chartRows.map((row) => (
                  <tr
                    key={row.wrong}
                    className={`${getGradeColor(row.pct)} text-white`}
                  >
                    <td className="py-1.5 text-center font-medium">{row.wrong}</td>
                    <td className="py-1.5 text-center font-medium">{formatPct(row.pct)}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </div>
  );
}

===== GradeCalculator.tsx (Average Grade Calculator) =====
import { useState, useMemo } from "react";

type Mode = "percentage" | "letters" | "points";

const LETTER_TO_PCT: Record<string, number> = {
  "A+": 98, "A": 95, "A-": 92,
  "B+": 88, "B": 85, "B-": 82,
  "C+": 78, "C": 75, "C-": 72,
  "D+": 68, "D": 65, "D-": 62,
  "F": 50,
};

const LETTER_OPTIONS = ["A+","A","A-","B+","B","B-","C+","C","C-","D+","D","D-","F"];

function getLetterGrade(pct: number): string {
  if (pct >= 97) return "A+";
  if (pct >= 93) return "A";
  if (pct >= 90) return "A-";
  if (pct >= 87) return "B+";
  if (pct >= 83) return "B";
  if (pct >= 80) return "B-";
  if (pct >= 77) return "C+";
  if (pct >= 73) return "C";
  if (pct >= 70) return "C-";
  if (pct >= 67) return "D+";
  if (pct >= 65) return "D";
  if (pct >= 60) return "D-";
  return "F";
}

interface Row {
  id: string;
  grade: string;
  weight: string;
}

const DEFAULT_ROWS: Row[] = [
  { id: "1", grade: "1", weight: "1" },
  { id: "2", grade: "2", weight: "2" },
  { id: "3", grade: "2", weight: "2" },
  { id: "4", grade: "9", weight: "8" },
  { id: "5", grade: "4", weight: "3" },
  { id: "6", grade: "8", weight: "5" },
  { id: "7", grade: "b", weight: "4" },
  { id: "8", grade: "10", weight: "5" },
  { id: "9", grade: "", weight: "" },
];

export function GradeCalculator() {
  const [mode, setMode] = useState<Mode>("percentage");
  const [rows, setRows] = useState<Row[]>(DEFAULT_ROWS);
  const [targetGrade, setTargetGrade] = useState("");

  const updateRow = (id: string, field: "grade" | "weight", value: string) => {
    setRows(rows.map((r) => (r.id === id ? { ...r, [field]: value } : r)));
  };

  const addRow = () => {
    setRows([...rows, { id: crypto.randomUUID(), grade: "", weight: "" }]);
  };

  const reset = () => {
    setRows(DEFAULT_ROWS.map(r => ({ ...r, grade: "", weight: "" })));
    setTargetGrade("");
  };

  const calculation = useMemo(() => {
    const validRows = rows.filter((r) => r.grade !== "" && r.grade !== undefined);

    if (validRows.length === 0) return { avg: null, letter: null, additional: null };

    let totalWeightedGrade = 0;
    let totalWeight = 0;
    let hasWeights = false;

    validRows.forEach((r) => {
      let gradeVal = 0;
      if (mode === "letters") {
        gradeVal = LETTER_TO_PCT[r.grade.toUpperCase()] ?? 0;
      } else if (mode === "points") {
        gradeVal = parseFloat(r.grade) || 0;
      } else {
        gradeVal = parseFloat(r.grade) || 0;
      }

      const w = parseFloat(r.weight);
      if (!isNaN(w) && w > 0) {
        hasWeights = true;
        totalWeightedGrade += gradeVal * w;
        totalWeight += w;
      } else {
        totalWeightedGrade += gradeVal;
        totalWeight += 1;
      }
    });

    const avg = totalWeight > 0 ? totalWeightedGrade / totalWeight : 0;
    const letter = mode !== "letters" ? getLetterGrade(avg) : getLetterGrade(avg);

    let additional: number | null = null;
    const target = parseFloat(targetGrade);
    if (!isNaN(target) && target > 0) {
      additional = target * (validRows.length + 1) - totalWeightedGrade / (hasWeights ? 1 : 1);
      const currentTotal = mode === "letters"
        ? validRows.reduce((sum, r) => sum + (LETTER_TO_PCT[r.grade.toUpperCase()] ?? 0), 0)
        : validRows.reduce((sum, r) => sum + (parseFloat(r.grade) || 0), 0);
      additional = target * (validRows.length + 1) - currentTotal;
    }

    return { avg, letter, additional };
  }, [rows, mode, targetGrade]);

  const subTabClass = (m: Mode) =>
    `flex-1 py-1.5 text-xs font-semibold text-center cursor-pointer transition-colors ${
      mode === m ? "bg-white text-gray-900 shadow-sm" : "text-gray-500 hover:text-gray-700"
    }`;

  return (
    <div className="max-w-2xl mx-auto space-y-4">
      {/* Sub-tabs */}
      <div className="bg-[#f0f0f0] rounded-md p-1 flex border border-gray-200">
        <button className={subTabClass("percentage")} onClick={() => setMode("percentage")}>
          Percentage
        </button>
        <button className={subTabClass("letters")} onClick={() => setMode("letters")}>
          Letters
        </button>
        <button className={subTabClass("points")} onClick={() => setMode("points")}>
          Points
        </button>
      </div>

      {/* Empty description area (matches reference) */}
      <div className="bg-white border border-gray-200 rounded-md px-4 py-3 min-h-[48px] text-xs text-gray-400">
        {mode === "percentage" && "Enter your grade percentage and optional weight for each row."}
        {mode === "letters" && "Enter your letter grade (A, B+, C-, etc.) and optional weight for each row."}
        {mode === "points" && "Enter points earned and optional weight for each row."}
      </div>

      {/* Table */}
      <div className="bg-white border border-gray-200 rounded-md overflow-hidden">
        <table className="w-full text-sm">
          <thead>
            <tr className="bg-[#f8f8f8] border-b border-gray-200">
              <th className="py-2 px-3 text-left text-xs font-semibold text-gray-600 w-10">#</th>
              <th className="py-2 px-3 text-left text-xs font-semibold text-gray-600">
                Grade {mode === "percentage" ? "(%)" : mode === "letters" ? "(Letter)" : "(Points)"}
              </th>
              <th className="py-2 px-3 text-left text-xs font-semibold text-gray-600">Weight</th>
            </tr>
          </thead>
          <tbody>
            {rows.map((row, i) => (
              <tr key={row.id} className="border-b border-gray-100 last:border-b-0">
                <td className="py-1.5 px-3 text-xs text-gray-500">{i + 1}</td>
                <td className="py-1.5 px-2">
                  {mode === "letters" ? (
                    <select
                      value={row.grade}
                      onChange={(e) => updateRow(row.id, "grade", e.target.value)}
                      className="w-full border border-gray-300 rounded px-2 py-1 text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400"
                    >
                      <option value=""></option>
                      {LETTER_OPTIONS.map((l) => (
                        <option key={l} value={l}>{l}</option>
                      ))}
                    </select>
                  ) : (
                    <input
                      type="number"
                      value={row.grade}
                      onChange={(e) => updateRow(row.id, "grade", e.target.value)}
                      className="w-full border border-gray-300 rounded px-2 py-1 text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400"
                    />
                  )}
                </td>
                <td className="py-1.5 px-2">
                  <input
                    type="number"
                    value={row.weight}
                    onChange={(e) => updateRow(row.id, "weight", e.target.value)}
                    className="w-full border border-gray-300 rounded px-2 py-1 text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400"
                  />
                </td>
              </tr>
            ))}
          </tbody>
        </table>

        {/* Target grade input */}
        <div className="px-4 py-3 border-t border-gray-100 bg-[#fafafa] flex flex-wrap items-center gap-2 text-sm text-gray-700">
          <span>Find additional grade needed to get average grade of</span>
          <input
            type="number"
            value={targetGrade}
            onChange={(e) => setTargetGrade(e.target.value)}
            className="w-20 border border-gray-300 rounded px-2 py-1 text-sm bg-white focus:outline-none focus:ring-1 focus:ring-blue-400"
          />
          <span>%</span>
        </div>

        {/* Buttons */}
        <div className="px-4 py-3 border-t border-gray-100 flex gap-2">
          <button
            onClick={reset}
            className="px-4 py-2 rounded text-sm font-semibold bg-[#1a1a2e] text-white hover:bg-[#2d2d60] hover:scale-105 hover:shadow-md transition-all duration-150"
          >
            Reset
          </button>
          <button
            onClick={addRow}
            className="px-4 py-2 rounded text-sm font-semibold bg-[#1a1a2e] text-white hover:bg-[#2d2d60] hover:scale-105 hover:shadow-md transition-all duration-150"
          >
            Add Row
          </button>
        </div>
      </div>

      {/* Results */}
      <div className="bg-white border border-gray-200 rounded-md px-4 py-4 space-y-3">
        <div className="text-sm font-semibold text-gray-700 text-center">Average Grade</div>
        <div className="flex gap-3">
          <div className="flex-1">
            <input
              readOnly
              value={calculation.avg !== null ? calculation.avg.toFixed(2) : ""}
              placeholder="0.00"
              className="w-full border border-gray-300 rounded px-3 py-1.5 text-sm bg-[#f8f8f8] text-gray-800 focus:outline-none"
            />
          </div>
          <div className="w-20">
            <input
              readOnly
              value={calculation.letter ?? ""}
              placeholder="—"
              className="w-full border border-gray-300 rounded px-3 py-1.5 text-sm bg-[#f8f8f8] text-gray-800 text-center focus:outline-none"
            />
          </div>
        </div>
        <div className="text-sm text-gray-600">Additional grade needed:</div>
        <input
          readOnly
          value={calculation.additional !== null && targetGrade !== "" ? calculation.additional.toFixed(2) : ""}
          placeholder="—"
          className="w-full border border-gray-300 rounded px-3 py-1.5 text-sm bg-[#f8f8f8] text-gray-800 focus:outline-none"
        />
      </div>
    </div>
  );
}

===== FinalGradeCalculator.tsx =====
import { useState, useMemo } from "react";

type Mode = "percentage" | "letters";

const LETTER_TO_PCT: Record<string, number> = {
  "A+": 98, "A": 95, "A-": 92,
  "B+": 88, "B": 85, "B-": 82,
  "C+": 78, "C": 75, "C-": 72,
  "D+": 68, "D": 65, "D-": 62,
  "F": 50,
};

const LETTER_OPTIONS = ["A+","A","A-","B+","B","B-","C+","C","C-","D+","D","D-","F"];

function getLetterGrade(pct: number): string {
  if (pct >= 97) return "A+";
  if (pct >= 93) return "A";
  if (pct >= 90) return "A-";
  if (pct >= 87) return "B+";
  if (pct >= 83) return "B";
  if (pct >= 80) return "B-";
  if (pct >= 77) return "C+";
  if (pct >= 73) return "C";
  if (pct >= 70) return "C-";
  if (pct >= 67) return "D+";
  if (pct >= 65) return "D";
  if (pct >= 60) return "D-";
  return "F";
}

export function FinalGradeCalculator() {
  const [mode, setMode] = useState<Mode>("percentage");
  const [currentGrade, setCurrentGrade] = useState("");
  const [desiredGrade, setDesiredGrade] = useState("");
  const [finalWeight, setFinalWeight] = useState("");

  const reset = () => {
    setCurrentGrade("");
    setDesiredGrade("");
    setFinalWeight("");
  };

  const calculation = useMemo(() => {
    const toNum = (val: string) =>
      mode === "letters" ? (LETTER_TO_PCT[val] ?? NaN) : parseFloat(val);

    const current = toNum(currentGrade);
    const desired = toNum(desiredGrade);
    const weight = parseFloat(finalWeight);

    if (isNaN(current) || isNaN(desired) || isNaN(weight) || weight <= 0 || weight >= 100) {
      return { required: null, letter: null };
    }

    const w = weight / 100;
    const required = (desired - current * (1 - w)) / w;
    const letter = isNaN(required) ? null : getLetterGrade(required);

    return { required, letter };
  }, [currentGrade, desiredGrade, finalWeight, mode]);

  const subTabBase =
    "flex-1 py-2 text-sm font-semibold text-center cursor-pointer transition-colors rounded-sm";
  const subTabActive = "bg-white text-gray-900 shadow-sm";
  const subTabInactive = "text-gray-500 hover:text-gray-700";

  const inputClass =
    "w-full border border-gray-300 rounded px-3 py-2 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-emerald-400";

  const selectClass =
    "w-full border border-gray-300 rounded px-3 py-2 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-emerald-400";

  return (
    <div className="max-w-2xl mx-auto space-y-4">
      {/* Scale selector */}
      <div className="text-sm text-gray-700 text-center font-medium">Select the grade scale:</div>

      <div className="bg-[#f0f0f0] rounded-md p-1 flex border border-gray-200">
        <button
          className={`${subTabBase} ${mode === "percentage" ? subTabActive : subTabInactive}`}
          onClick={() => setMode("percentage")}
        >
          Percentage
        </button>
        <button
          className={`${subTabBase} ${mode === "letters" ? subTabActive : subTabInactive}`}
          onClick={() => setMode("letters")}
        >
          Letters
        </button>
      </div>

      {/* Inputs */}
      <div className="bg-[#f0f0f0] rounded-md px-6 py-5 space-y-4">
        {/* Current Grade */}
        <div>
          <label className="block text-sm font-medium text-gray-700 text-center mb-1">
            Current Grade
          </label>
          {mode === "percentage" ? (
            <div className="flex items-center gap-2">
              <input
                type="number"
                value={currentGrade}
                onChange={(e) => setCurrentGrade(e.target.value)}
                placeholder=""
                className={inputClass}
              />
              <span className="text-sm text-gray-600 font-medium">%</span>
            </div>
          ) : (
            <select
              value={currentGrade}
              onChange={(e) => setCurrentGrade(e.target.value)}
              className={selectClass}
            >
              <option value=""></option>
              {LETTER_OPTIONS.map((l) => (
                <option key={l} value={l}>{l}</option>
              ))}
            </select>
          )}
        </div>

        {/* Desired Grade */}
        <div>
          <label className="block text-sm font-medium text-gray-700 text-center mb-1">
            Desired Grade
          </label>
          {mode === "percentage" ? (
            <div className="flex items-center gap-2">
              <input
                type="number"
                value={desiredGrade}
                onChange={(e) => setDesiredGrade(e.target.value)}
                placeholder=""
                className={inputClass}
              />
              <span className="text-sm text-gray-600 font-medium">%</span>
            </div>
          ) : (
            <select
              value={desiredGrade}
              onChange={(e) => setDesiredGrade(e.target.value)}
              className={selectClass}
            >
              <option value=""></option>
              {LETTER_OPTIONS.map((l) => (
                <option key={l} value={l}>{l}</option>
              ))}
            </select>
          )}
        </div>

        {/* Final Exam Weight */}
        <div>
          <label className="block text-sm font-medium text-gray-700 text-center mb-1">
            Final Exam Weight
          </label>
          <div className="flex items-center gap-2">
            <input
              type="number"
              value={finalWeight}
              onChange={(e) => setFinalWeight(e.target.value)}
              placeholder=""
              className={inputClass}
            />
            <span className="text-sm text-gray-600 font-medium">%</span>
          </div>
        </div>

        {/* Reset */}
        <div className="pt-1">
          <button
            onClick={reset}
            className="px-6 py-2 rounded text-sm font-bold bg-gradient-to-br from-emerald-500 to-teal-600 text-white hover:from-emerald-600 hover:to-teal-700 hover:scale-105 hover:shadow-md transition-all duration-150 shadow-sm"
          >
            Reset
          </button>
        </div>
      </div>

      {/* Result */}
      <div className="bg-[#f0f0f0] rounded-md px-6 py-4 space-y-3">
        <div className="text-sm font-medium text-gray-700 text-center">
          Final Exam Grade Needed:
        </div>
        <div className="flex gap-3">
          <input
            readOnly
            value={
              calculation.required !== null
                ? calculation.required < 0
                  ? "0.00%"
                  : `${calculation.required.toFixed(2)}%`
                : ""
            }
            placeholder=""
            className="flex-1 border border-gray-300 rounded px-3 py-2 text-sm bg-white text-gray-800 focus:outline-none"
          />
          <input
            readOnly
            value={calculation.letter ?? ""}
            placeholder=""
            className="w-24 border border-gray-300 rounded px-3 py-2 text-sm bg-white text-gray-800 text-center focus:outline-none"
          />
        </div>
        {calculation.required !== null && (
          <p className={`text-xs text-center font-medium ${
            calculation.required > 100
              ? "text-red-500"
              : calculation.required <= 0
              ? "text-emerald-600"
              : "text-gray-600"
          }`}>
            {calculation.required > 100
              ? "This score exceeds 100% — consider extra credit options."
              : calculation.required <= 0
              ? "You've already achieved your desired grade!"
              : "You've got this — study hard!"}
          </p>
        )}
      </div>
    </div>
  );
}

===== Home.tsx =====
import { useState } from "react";
import { cn } from "@/lib/utils";
import { EasyGrader } from "@/components/calculators/EasyGrader";
import { GradeCalculator } from "@/components/calculators/GradeCalculator";
import { FinalGradeCalculator } from "@/components/calculators/FinalGradeCalculator";

type TabId = "easy" | "average" | "final";

interface TabConfig {
  id: TabId;
  label: string;
  title: string;
  subtitle: string;
  gradient: string;
  activeGradient: string;
  ring: string;
}

const TABS: TabConfig[] = [
  {
    id: "easy",
    label: "Easy Grader",
    title: "Grade Calculator",
    subtitle: "Use this simple EZ Grading calculator to find quiz, test and assignment scores:",
    gradient: "from-orange-400 to-rose-500 hover:from-orange-500 hover:to-rose-600",
    activeGradient: "from-orange-500 to-rose-600",
    ring: "ring-orange-300/50",
  },
  {
    id: "average",
    label: "Average Grade Calculator",
    title: "Average Grade Calculator",
    subtitle: "Enter grades and weights:",
    gradient: "from-violet-500 to-indigo-600 hover:from-violet-600 hover:to-indigo-700",
    activeGradient: "from-violet-600 to-indigo-700",
    ring: "ring-violet-300/50",
  },
  {
    id: "final",
    label: "Final Grade Calculator",
    title: "Final Grade Calculator",
    subtitle: "Find out what grade you need on your final exam:",
    gradient: "from-emerald-400 to-teal-600 hover:from-emerald-500 hover:to-teal-700",
    activeGradient: "from-emerald-500 to-teal-700",
    ring: "ring-emerald-300/50",
  },
];

export default function Home() {
  const [activeTab, setActiveTab] = useState<TabId>("easy");
  const activeConfig = TABS.find((t) => t.id === activeTab)!;

  return (
    <div className="min-h-screen bg-white">
      <main className="max-w-3xl mx-auto px-4 py-10">

        {/* Header — changes per tab */}
        <div className="text-center mb-6">
          <h1 className="text-3xl font-bold text-gray-900 mb-2">{activeConfig.title}</h1>
          <p className="text-gray-600 text-sm leading-relaxed">{activeConfig.subtitle}</p>
        </div>

        {/* Tab Buttons */}
        <div className="flex flex-wrap justify-center gap-3 mb-7">
          {TABS.map((tab) => {
            const isActive = activeTab === tab.id;
            return (
              <button
                key={tab.id}
                onClick={() => setActiveTab(tab.id)}
                className={cn(
                  "px-5 py-2.5 rounded-lg text-sm font-bold text-white transition-all duration-200",
                  "bg-gradient-to-br shadow-md",
                  isActive
                    ? `${tab.activeGradient} scale-105 shadow-lg ring-2 ${tab.ring}`
                    : `${tab.gradient} hover:scale-105 hover:shadow-lg`
                )}
              >
                {tab.label}
              </button>
            );
          })}
        </div>

        {/* Content */}
        <div>
          {activeTab === "easy" && <EasyGrader />}
          {activeTab === "average" && <GradeCalculator />}
          {activeTab === "final" && <FinalGradeCalculator />}
        </div>

      </main>
    </div>
  );
}
Scroll to Top