https://re-wise-psi.vercel.app I want to create a website like this for KUHS I have data
https://re-wise-psi.vercel.app
Create a complete HTML/CSS/JS template website once you share the data
mkdir -p /home/daytona/workspace/kuhs-qbank && ls /home/daytona/workspace/kuhs-q…
~/kuhs-qbank/data.js
// ============================================================
// KUHS 1st Year MBBS Question Bank — Data File
// Replace the sample questions below with your real data.
// Structure per question:
// {
// id : unique number,
// subject : "Anatomy" | "Physiology" | "Biochemistry",
// topic : sub-topic string,
// type : "LAQ" | "SAQ" | "MCQ",
// year : exam year string e.g. "2022",
// question : question text,
// answer : answer text (optional — used in browse mode),
// options : [...] only for MCQ,
// correct : index (0-based) only for MCQ
// }
// ============================================================
const QUESTIONS = [
// ── ANATOMY ─────────────────────────────────────────────
{
id: 1, subject: "Anatomy", topic: "Upper Limb", type: "LAQ", year: "2023",
question: "Describe the brachial plexus — its formation, branches and clinical importance.",
answer: "The brachial plexus is formed by the ventral rami of C5–T1 nerve roots. It has roots, trunks, divisions, cords and terminal branches. Terminal branches include musculocutaneous, median, ulnar, radial and axillary nerves. Injury at Erb's point (C5,C6) causes Erb's palsy (waiter's tip position). Klumpke's palsy involves C8,T1 with claw hand and Horner's syndrome."
},
{
id: 2, subject: "Anatomy", topic: "Upper Limb", type: "SAQ", year: "2023",
question: "Write a short note on the anatomical snuff box.",
answer: "The anatomical snuff box is a triangular depression on the lateral aspect of the wrist. Boundaries: laterally — tendons of APL and EPB; medially — EPL. Floor: styloid process of radius, scaphoid, trapezium. Contents: radial artery, cephalic vein, superficial branch of radial nerve. Clinical: tenderness here after a fall suggests scaphoid fracture."
},
{
id: 3, subject: "Anatomy", topic: "Upper Limb", type: "MCQ", year: "2022",
question: "Which nerve is most commonly injured in fracture of the surgical neck of the humerus?",
options: ["Radial nerve", "Axillary nerve", "Musculocutaneous nerve", "Median nerve"],
correct: 1,
answer: "Axillary nerve — it winds around the surgical neck of the humerus and is vulnerable in fractures at that level, causing weakness of deltoid and loss of sensation over the regimental badge area."
},
{
id: 4, subject: "Anatomy", topic: "Lower Limb", type: "LAQ", year: "2022",
question: "Describe the femoral triangle — its boundaries, contents and clinical significance.",
answer: "Boundaries: superiorly — inguinal ligament; laterally — medial border of sartorius; medially — medial border of adductor longus; roof — fascia lata; floor — iliopsoas, pectineus, adductor longus. Contents (lateral to medial): femoral nerve, femoral artery, femoral vein, femoral canal (NAVY mnemonic reversed). Clinical: femoral hernia passes through femoral canal medial to the vein; femoral pulse palpable at midinguinal point; femoral artery catheterisation site."
},
{
id: 5, subject: "Anatomy", topic: "Lower Limb", type: "SAQ", year: "2021",
question: "Write a short note on the popliteal fossa.",
answer: "The popliteal fossa is a diamond-shaped space behind the knee. Boundaries: superomedially — semimembranosus and semitendinosus; superolaterally — biceps femoris; inferomedially — medial head of gastrocnemius; inferolaterally — lateral head of gastrocnemius and plantaris. Contents: popliteal artery and vein (deepest to most superficial), tibial nerve, common fibular nerve, small saphenous vein, posterior cutaneous nerve of thigh, popliteal lymph nodes."
},
{
id: 6, subject: "Anatomy", topic: "Head & Neck", type: "MCQ", year: "2023",
question: "The cavernous sinus receives blood from which of the following veins?",
options: ["Internal jugular vein", "Superior ophthalmic vein", "External jugular vein", "Occipital vein"],
correct: 1,
answer: "Superior ophthalmic vein drains into the cavernous sinus, forming an important route for spread of infection from the danger area of the face."
},
{
id: 7, subject: "Anatomy", topic: "Thorax", type: "SAQ", year: "2022",
question: "Write a short note on the bronchopulmonary segments.",
answer: "A bronchopulmonary segment is the smallest surgically resectable unit of lung. Each segment has its own segmental bronchus, artery, and intersegmental veins. Right lung: 10 segments (3 in upper, 2 in middle, 5 in lower lobe). Left lung: 8–10 segments. Clinical importance: localisation of tuberculosis, bronchiectasis, and abscess; surgical resection without sacrificing adjacent healthy tissue."
},
{
id: 8, subject: "Anatomy", topic: "Abdomen", type: "LAQ", year: "2021",
question: "Describe the inguinal canal — its walls, contents and clinical importance.",
answer: "The inguinal canal is a 4 cm oblique passage through the anterior abdominal wall. It runs from deep inguinal ring to superficial inguinal ring. Walls: anterior — external oblique aponeurosis (+ internal oblique laterally); posterior — transversalis fascia (+ conjoint tendon medially); roof — arched fibres of internal oblique and transversus abdominis; floor — inguinal ligament. Contents in males: spermatic cord (vas deferens, testicular artery, pampiniform plexus, lymphatics, genital branch of genitofemoral nerve, ilioinguinal nerve). Females: round ligament. Clinical: site of indirect inguinal hernia (through deep ring) and direct inguinal hernia (through Hesselbach triangle)."
},
// ── PHYSIOLOGY ──────────────────────────────────────────
{
id: 9, subject: "Physiology", topic: "Blood", type: "LAQ", year: "2023",
question: "Describe the mechanism of haemostasis with a note on the coagulation cascade.",
answer: "Haemostasis has four phases: (1) Vascular spasm — vasoconstriction. (2) Primary haemostasis — platelet plug formation via adhesion (vWF, GpIb), activation, and aggregation (GpIIb/IIIa, fibrinogen). (3) Secondary haemostasis — coagulation cascade: Extrinsic (tissue factor + VII) and Intrinsic (XII→XI→IX→VIII) pathways converge at factor X. Prothrombinase complex converts prothrombin to thrombin; thrombin converts fibrinogen to fibrin. (4) Fibrinolysis — plasmin degrades fibrin clot. Tests: PT (extrinsic), APTT (intrinsic), platelet count."
},
{
id: 10, subject: "Physiology", topic: "Blood", type: "SAQ", year: "2022",
question: "Write a short note on the ABO blood group system.",
answer: "ABO system is based on antigens (agglutinogens) on RBC surface and antibodies (agglutinins) in plasma. Blood group A: A antigen, anti-B antibody. Group B: B antigen, anti-A. Group AB: both antigens, no antibody (universal recipient). Group O: no antigens, both antibodies (universal donor). Inheritance: IA, IB, i alleles. Importance: blood transfusion compatibility, organ transplantation, haemolytic disease of newborn."
},
{
id: 11, subject: "Physiology", topic: "CVS", type: "MCQ", year: "2023",
question: "The normal PR interval on ECG is:",
options: ["0.04–0.08 sec", "0.12–0.20 sec", "0.25–0.35 sec", "0.40–0.50 sec"],
correct: 1,
answer: "Normal PR interval is 0.12–0.20 seconds (3–5 small squares). It represents time for impulse to travel from SA node through atria, AV node, and bundle of His to the ventricles."
},
{
id: 12, subject: "Physiology", topic: "CVS", type: "LAQ", year: "2022",
question: "Describe the cardiac cycle with reference to pressure and volume changes in the left ventricle.",
answer: "The cardiac cycle lasts ~0.8 sec at 75 bpm. Phases: (1) Isovolumetric contraction — LV pressure rises, all valves closed, no change in volume. (2) Rapid ejection — aortic valve opens when LV pressure > aortic pressure (~80 mmHg); SV (~70 mL) is ejected. (3) Reduced ejection. (4) Isovolumetric relaxation — all valves closed, LV pressure falls. (5) Rapid filling — mitral valve opens when LV pressure < LA pressure. (6) Slow filling (diastasis). (7) Atrial systole. EDV ~120 mL, ESV ~50 mL, SV ~70 mL, EF ~58%."
},
{
id: 13, subject: "Physiology", topic: "Respiratory", type: "SAQ", year: "2023",
question: "Write a short note on the oxygen-haemoglobin dissociation curve.",
answer: "The O₂-Hb dissociation curve is sigmoid-shaped due to cooperative binding of O₂ to haemoglobin. P50 = 26 mmHg (PO₂ at which Hb is 50% saturated). Right shift (decreased affinity — more O₂ released): increased CO₂, H⁺, temperature, 2,3-BPG (Bohr effect). Left shift (increased affinity): decreased CO₂, H⁺, temperature, 2,3-BPG; HbF, CO poisoning. Physiological importance: facilitates O₂ loading in lungs and unloading in tissues."
},
{
id: 14, subject: "Physiology", topic: "Renal", type: "MCQ", year: "2022",
question: "Normal GFR (Glomerular Filtration Rate) in adults is approximately:",
options: ["60 mL/min", "90 mL/min", "125 mL/min", "180 mL/min"],
correct: 2,
answer: "Normal GFR is approximately 125 mL/min (180 L/day). Of this, ~99% is reabsorbed, with only ~1.5 L excreted as urine per day."
},
{
id: 15, subject: "Physiology", topic: "Nervous System", type: "SAQ", year: "2021",
question: "Describe the generation and propagation of an action potential.",
answer: "Resting membrane potential = −70 mV. Depolarisation: stimulus opens voltage-gated Na⁺ channels → Na⁺ influx → membrane potential rises to +30 mV. Repolarisation: Na⁺ channels inactivate; K⁺ channels open → K⁺ efflux → membrane returns to −70 mV. Hyperpolarisation (after-potential). Refractory periods: absolute (no new AP possible) and relative (stronger stimulus needed). Propagation is unidirectional due to refractory period. Saltatory conduction in myelinated fibres (faster)."
},
{
id: 16, subject: "Physiology", topic: "Endocrine", type: "LAQ", year: "2023",
question: "Describe the synthesis, secretion, actions and regulation of insulin.",
answer: "Synthesis: β cells of islets of Langerhans; preproinsulin → proinsulin → insulin + C-peptide. Secretion: triggered by hyperglycaemia (glucose sensor via GLUT2 + glucokinase → ATP → K⁺-ATP channel closure → depolarisation → Ca²⁺ entry → exocytosis). Actions: increases glucose uptake (GLUT4 in muscle/fat), glycogen synthesis, protein synthesis; inhibits gluconeogenesis, lipolysis, ketogenesis. Regulation: stimulated by high glucose, GIP, GLP-1, vagus, amino acids; inhibited by somatostatin, sympathetic activity, low glucose. Deficiency → Type 1 DM."
},
// ── BIOCHEMISTRY ────────────────────────────────────────
{
id: 17, subject: "Biochemistry", topic: "Proteins", type: "LAQ", year: "2023",
question: "Describe the structure of proteins with suitable examples at each level.",
answer: "Primary structure: sequence of amino acids linked by peptide bonds (e.g., insulin). Secondary structure: local folding — α-helix (stabilised by H-bonds between NH and C=O 4 residues apart; e.g., keratin) and β-pleated sheet (parallel or antiparallel; e.g., fibroin). Tertiary structure: 3D folding of a single polypeptide — stabilised by H-bonds, ionic bonds, disulphide bridges, hydrophobic interactions (e.g., myoglobin). Quaternary structure: association of two or more polypeptide chains (subunits; e.g., haemoglobin — 2α + 2β chains). Denaturation disrupts secondary/tertiary/quaternary without breaking peptide bonds."
},
{
id: 18, subject: "Biochemistry", topic: "Carbohydrates", type: "SAQ", year: "2022",
question: "Write a short note on glycolysis.",
answer: "Glycolysis is the breakdown of glucose to pyruvate in the cytoplasm (10 steps). Investment phase: 2 ATP consumed. Payoff phase: 4 ATP produced. Net gain: 2 ATP, 2 NADH, 2 pyruvate per glucose. Key enzymes: hexokinase (step 1), phosphofructokinase-1 (step 3 — rate-limiting, inhibited by ATP/citrate, stimulated by AMP/fructose-2,6-bisphosphate), pyruvate kinase (step 10). Regulation is at these three irreversible steps. In anaerobic conditions, pyruvate → lactate (regenerates NAD⁺). Total yield (aerobic): ~30–32 ATP per glucose."
},
{
id: 19, subject: "Biochemistry", topic: "Carbohydrates", type: "MCQ", year: "2023",
question: "The rate-limiting enzyme of glycolysis is:",
options: ["Hexokinase", "Aldolase", "Phosphofructokinase-1", "Pyruvate kinase"],
correct: 2,
answer: "Phosphofructokinase-1 (PFK-1) catalyses the phosphorylation of fructose-6-phosphate to fructose-1,6-bisphosphate and is the key regulatory/rate-limiting enzyme of glycolysis."
},
{
id: 20, subject: "Biochemistry", topic: "Lipids", type: "SAQ", year: "2023",
question: "Write a short note on cholesterol synthesis.",
answer: "Cholesterol synthesis occurs mainly in the liver. Key steps: Acetyl-CoA → HMG-CoA → mevalonate (catalysed by HMG-CoA reductase — rate-limiting; inhibited by statins) → squalene → lanosterol → cholesterol. Regulation: SREBP transcription factor upregulates HMG-CoA reductase when cellular cholesterol is low; statins competitively inhibit HMG-CoA reductase. Cholesterol is a precursor for bile acids, steroid hormones, vitamin D, and cell membranes."
},
{
id: 21, subject: "Biochemistry", topic: "Enzymes", type: "LAQ", year: "2022",
question: "Describe enzyme kinetics with reference to the Michaelis-Menten equation.",
answer: "The Michaelis-Menten equation: V = Vmax[S] / (Km + [S]). Vmax = maximum velocity (all enzyme sites saturated). Km = substrate concentration at half-Vmax; reflects enzyme-substrate affinity (low Km = high affinity). Lineweaver-Burk plot (double reciprocal) linearises the hyperbola. Inhibition types: Competitive — inhibitor resembles substrate; raises apparent Km, Vmax unchanged; overcome by excess substrate. Non-competitive — inhibitor binds elsewhere; lowers Vmax, Km unchanged. Uncompetitive — binds ES complex; lowers both Km and Vmax. Clinical: enzyme kinetics relevant to drug design, toxicology (e.g., methotrexate as competitive inhibitor of DHFR)."
},
{
id: 22, subject: "Biochemistry", topic: "Vitamins", type: "MCQ", year: "2021",
question: "Deficiency of which vitamin causes Wernicke's encephalopathy?",
options: ["Vitamin B2 (Riboflavin)", "Vitamin B1 (Thiamine)", "Vitamin B6 (Pyridoxine)", "Vitamin B12 (Cobalamin)"],
correct: 1,
answer: "Thiamine (Vitamin B1) deficiency causes Wernicke's encephalopathy (confusion, ophthalmoplegia, ataxia — classic triad). Thiamine is a cofactor for pyruvate dehydrogenase, α-ketoglutarate dehydrogenase, and transketolase."
},
{
id: 23, subject: "Biochemistry", topic: "Molecular Biology", type: "SAQ", year: "2023",
question: "Write a short note on the PCR (Polymerase Chain Reaction).",
answer: "PCR amplifies specific DNA sequences in vitro. Steps (repeated ~30–40 cycles): (1) Denaturation — heat to 94°C, double-stranded DNA separates. (2) Annealing — cool to 50–65°C, primers bind to complementary sequences. (3) Extension — 72°C, Taq polymerase synthesises new DNA strands. Each cycle doubles the target DNA (2ⁿ copies after n cycles). Applications: diagnosis of genetic diseases, infectious diseases (COVID PCR), forensics, cloning, sequencing."
}
];
// ── Utility helpers ──────────────────────────────────────
const SUBJECTS = [...new Set(QUESTIONS.map(q => q.subject))].sort();
const TYPES = [...new Set(QUESTIONS.map(q => q.type))].sort();
const YEARS = [...new Set(QUESTIONS.map(q => q.year))].sort().reverse();
function filterQuestions({ subjects = [], types = [], years = [] } = {}) {
return QUESTIONS.filter(q =>
(subjects.length === 0 || subjects.includes(q.subject)) &&
(types.length === 0 || types.includes(q.type)) &&
(years.length === 0 || years.includes(q.year))
);
}
function shuffle(arr) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
~/kuhs-qbank/styles.css
/* =========================================================
KUHS 1st Year MBBS Question Bank — Global Stylesheet
========================================================= */
/* ── Reset & Root ──────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--primary : #2563eb;
--primary-d : #1d4ed8;
--primary-l : #dbeafe;
--accent : #7c3aed;
--accent-l : #ede9fe;
--success : #16a34a;
--success-l : #dcfce7;
--danger : #dc2626;
--danger-l : #fee2e2;
--warn : #d97706;
--warn-l : #fef3c7;
--bg : #f8fafc;
--surface : #ffffff;
--border : #e2e8f0;
--text : #0f172a;
--text-muted : #64748b;
--text-light : #94a3b8;
--radius : 12px;
--radius-sm : 8px;
--shadow-sm : 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06);
--shadow : 0 4px 16px rgba(0,0,0,.10);
--shadow-lg : 0 10px 40px rgba(0,0,0,.14);
--font : 'Inter', system-ui, -apple-system, sans-serif;
--transition : .2s cubic-bezier(.4,0,.2,1);
}
html { font-size: 16px; scroll-behavior: smooth; }
body {
font-family: var(--font);
background: var(--bg);
color: var(--text);
min-height: 100vh;
line-height: 1.6;
}
/* ── Typography ────────────────────────────────────────── */
h1, h2, h3, h4 { font-weight: 700; line-height: 1.25; }
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.125rem; }
p { color: var(--text-muted); }
a { color: var(--primary); text-decoration: none; }
/* ── Navbar ────────────────────────────────────────────── */
.navbar {
background: var(--surface);
border-bottom: 1px solid var(--border);
padding: .875rem 1.5rem;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 100;
box-shadow: var(--shadow-sm);
}
.navbar-brand {
display: flex;
align-items: center;
gap: .625rem;
font-weight: 800;
font-size: 1.25rem;
color: var(--text);
text-decoration: none;
}
.navbar-brand .logo-icon {
width: 36px; height: 36px;
background: linear-gradient(135deg, var(--primary), var(--accent));
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
color: white; font-size: 1rem; font-weight: 800;
}
.navbar-brand span.sub { font-weight: 400; font-size: .8rem; color: var(--text-muted); margin-left: .25rem; }
.navbar-links { display: flex; gap: .25rem; }
.navbar-links a {
padding: .45rem .9rem;
border-radius: var(--radius-sm);
font-size: .875rem;
font-weight: 500;
color: var(--text-muted);
transition: var(--transition);
}
.navbar-links a:hover,
.navbar-links a.active { background: var(--primary-l); color: var(--primary); }
/* ── Layout Helpers ────────────────────────────────────── */
.container { max-width: 1100px; margin: 0 auto; padding: 0 1.5rem; }
.page-wrap { padding: 2.5rem 0 4rem; }
/* ── Cards ─────────────────────────────────────────────── */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.5rem;
box-shadow: var(--shadow-sm);
}
.card-sm { padding: 1rem 1.25rem; }
/* ── Buttons ────────────────────────────────────────────── */
.btn {
display: inline-flex; align-items: center; gap: .4rem;
padding: .625rem 1.25rem;
border-radius: var(--radius-sm);
font-size: .875rem; font-weight: 600;
cursor: pointer; border: none; transition: var(--transition);
text-decoration: none;
}
.btn-primary { background: var(--primary); color: #fff; }
.btn-primary:hover { background: var(--primary-d); }
.btn-outline {
background: transparent; color: var(--primary);
border: 2px solid var(--primary);
}
.btn-outline:hover { background: var(--primary-l); }
.btn-ghost {
background: transparent; color: var(--text-muted);
border: 1px solid var(--border);
}
.btn-ghost:hover { background: var(--bg); color: var(--text); }
.btn-success { background: var(--success); color: #fff; }
.btn-success:hover { background: #15803d; }
.btn-danger { background: var(--danger); color: #fff; }
.btn-danger:hover { background: #b91c1c; }
.btn-lg { padding: .875rem 1.75rem; font-size: 1rem; border-radius: var(--radius); }
.btn-sm { padding: .375rem .875rem; font-size: .8rem; }
.btn:disabled { opacity: .45; cursor: not-allowed; }
/* ── Subject Badge pills ────────────────────────────────── */
.badge {
display: inline-flex; align-items: center;
padding: .25rem .75rem;
border-radius: 999px;
font-size: .75rem; font-weight: 600;
letter-spacing: .02em;
}
.badge-anatomy { background: #dbeafe; color: #1d4ed8; }
.badge-physiology { background: #dcfce7; color: #15803d; }
.badge-biochemistry { background: #ede9fe; color: #6d28d9; }
.badge-laq { background: #fef3c7; color: #92400e; }
.badge-saq { background: #e0f2fe; color: #0369a1; }
.badge-mcq { background: #fce7f3; color: #9d174d; }
/* ── Tag-style toggles (subject/type selectors) ─────────── */
.tag-group { display: flex; flex-wrap: wrap; gap: .5rem; }
.tag {
padding: .4rem 1rem;
border-radius: 999px;
border: 2px solid var(--border);
font-size: .8rem; font-weight: 600;
cursor: pointer; transition: var(--transition);
background: var(--surface); color: var(--text-muted);
user-select: none;
}
.tag:hover { border-color: var(--primary); color: var(--primary); }
.tag.selected { background: var(--primary); border-color: var(--primary); color: #fff; }
.tag.anatomy.selected { background: var(--primary); border-color: var(--primary); }
.tag.physiology.selected { background: var(--success); border-color: var(--success); }
.tag.biochemistry.selected { background: var(--accent); border-color: var(--accent); }
/* ── Mode toggle ────────────────────────────────────────── */
.mode-toggle {
display: flex;
background: var(--bg);
border-radius: var(--radius-sm);
border: 1px solid var(--border);
overflow: hidden;
}
.mode-btn {
padding: .55rem 1.1rem;
font-size: .875rem; font-weight: 600;
cursor: pointer; border: none; background: transparent; color: var(--text-muted);
transition: var(--transition);
}
.mode-btn.active { background: var(--primary); color: #fff; }
/* ── Progress bar ───────────────────────────────────────── */
.progress-wrap { background: var(--border); border-radius: 999px; height: 8px; overflow: hidden; }
.progress-bar { height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--primary), var(--accent)); transition: width .4s ease; }
/* ── Stat cards ─────────────────────────────────────────── */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; }
.stat-card {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--radius); padding: 1.25rem 1rem; text-align: center;
}
.stat-card .stat-value { font-size: 2rem; font-weight: 800; color: var(--primary); }
.stat-card .stat-label { font-size: .8rem; color: var(--text-muted); font-weight: 500; margin-top: .2rem; }
/* ── Bar chart (pure CSS) ───────────────────────────────── */
.bar-row { display: flex; align-items: center; gap: .75rem; margin-bottom: .625rem; }
.bar-label { width: 110px; font-size: .8rem; font-weight: 600; color: var(--text); flex-shrink: 0; }
.bar-track { flex: 1; background: var(--border); border-radius: 999px; height: 14px; }
.bar-fill { height: 100%; border-radius: 999px; transition: width .6s ease; }
.bar-count { width: 36px; text-align: right; font-size: .8rem; color: var(--text-muted); flex-shrink: 0; }
/* ── Flashcard ──────────────────────────────────────────── */
.flashcard-scene { perspective: 1200px; width: 100%; }
.flashcard {
width: 100%; min-height: 300px;
position: relative; transform-style: preserve-3d;
transition: transform .55s cubic-bezier(.4,0,.2,1);
cursor: pointer;
}
.flashcard.flipped { transform: rotateY(180deg); }
.flashcard-face {
position: absolute; width: 100%; min-height: 300px;
backface-visibility: hidden;
border-radius: var(--radius);
padding: 2rem;
display: flex; flex-direction: column;
justify-content: center; align-items: center;
text-align: center;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.flashcard-front { background: var(--surface); }
.flashcard-back {
background: linear-gradient(135deg, #eff6ff 0%, #f5f3ff 100%);
transform: rotateY(180deg);
}
.flashcard-hint {
position: absolute; bottom: 1rem; right: 1.25rem;
font-size: .72rem; color: var(--text-light); font-style: italic;
}
.flashcard-q { font-size: 1.15rem; font-weight: 600; color: var(--text); line-height: 1.5; }
.flashcard-a { font-size: .9rem; color: var(--text); line-height: 1.7; text-align: left; }
/* MCQ options ─────────────────────────────────────────── */
.options-list { list-style: none; width: 100%; text-align: left; margin-top: 1rem; }
.options-list li {
padding: .65rem 1rem;
border-radius: var(--radius-sm);
border: 2px solid var(--border);
margin-bottom: .5rem;
cursor: pointer; transition: var(--transition);
font-size: .9rem;
}
.options-list li:hover:not(.locked) { border-color: var(--primary); background: var(--primary-l); }
.options-list li.correct { border-color: var(--success); background: var(--success-l); color: var(--success); font-weight: 600; }
.options-list li.wrong { border-color: var(--danger); background: var(--danger-l); color: var(--danger); }
/* ── Browse / Question list ─────────────────────────────── */
.q-item {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.25rem 1.5rem;
margin-bottom: .875rem;
transition: var(--transition);
}
.q-item:hover { border-color: var(--primary); box-shadow: var(--shadow); }
.q-item-header {
display: flex; align-items: flex-start;
justify-content: space-between; gap: 1rem;
}
.q-item-meta { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; margin-bottom: .5rem; }
.q-item-body { font-weight: 600; font-size: .95rem; color: var(--text); margin-bottom: .5rem; }
.q-item-answer {
background: var(--bg); border-left: 4px solid var(--primary);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
padding: .875rem 1rem;
font-size: .875rem; color: var(--text);
margin-top: .75rem; line-height: 1.7;
display: none;
}
.q-item-answer.open { display: block; }
.toggle-ans {
font-size: .75rem; color: var(--primary);
font-weight: 600; cursor: pointer;
padding: .2rem .5rem; border-radius: 4px;
}
.toggle-ans:hover { background: var(--primary-l); }
/* ── Search & Filter bar ────────────────────────────────── */
.filter-bar {
display: flex; align-items: center;
gap: .75rem; flex-wrap: wrap;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: .875rem 1.25rem;
margin-bottom: 1.5rem;
}
.search-input {
flex: 1; min-width: 220px;
padding: .55rem 1rem;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
font-size: .875rem;
outline: none; transition: var(--transition);
}
.search-input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(37,99,235,.12); }
.filter-select {
padding: .55rem .875rem;
border-radius: var(--radius-sm);
border: 1px solid var(--border);
font-size: .875rem;
background: var(--surface); color: var(--text);
cursor: pointer; outline: none;
}
.filter-select:focus { border-color: var(--primary); }
/* ── Section headings ───────────────────────────────────── */
.section-title {
font-size: 1.25rem; font-weight: 700;
color: var(--text); margin-bottom: 1rem;
display: flex; align-items: center; gap: .5rem;
}
.section-title .count {
background: var(--primary-l); color: var(--primary);
border-radius: 999px; padding: .1rem .6rem;
font-size: .75rem; font-weight: 700;
}
/* ── Hero banner (index) ────────────────────────────────── */
.hero {
background: linear-gradient(135deg, #1e3a8a 0%, #4f46e5 50%, #7c3aed 100%);
color: white; padding: 3rem 0 2.5rem;
}
.hero h1 { font-size: 2.5rem; margin-bottom: .5rem; }
.hero p { color: rgba(255,255,255,.8); font-size: 1.05rem; }
.hero .badge-wrap { margin-top: .75rem; display: flex; gap: .5rem; flex-wrap: wrap; }
.hero .hero-badge {
background: rgba(255,255,255,.15);
color: white; border-radius: 999px;
padding: .25rem .875rem; font-size: .8rem; font-weight: 600;
}
/* ── Donut chart placeholder ────────────────────────────── */
.donut-wrap { display: flex; justify-content: center; padding: 1rem 0; }
canvas { max-width: 100%; }
/* ── Empty state ────────────────────────────────────────── */
.empty-state {
text-align: center; padding: 3rem 1rem;
color: var(--text-muted);
}
.empty-state .icon { font-size: 3rem; margin-bottom: 1rem; }
/* ── Footer ─────────────────────────────────────────────── */
.footer {
background: var(--surface); border-top: 1px solid var(--border);
text-align: center; padding: 1.25rem;
font-size: .8rem; color: var(--text-muted);
margin-top: auto;
}
/* ── Responsive ─────────────────────────────────────────── */
@media (max-width: 640px) {
.hero h1 { font-size: 1.75rem; }
.navbar-links a { padding: .35rem .6rem; font-size: .8rem; }
.flashcard-face { padding: 1.25rem; min-height: 240px; }
.flashcard-q { font-size: 1rem; }
h2 { font-size: 1.25rem; }
}
~/kuhs-qbank/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KUHS 1st Year MBBS — Question Bank</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
<style>
.setup-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-top: 2rem; }
@media(max-width:700px){ .setup-grid { grid-template-columns:1fr; } }
.setup-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:1.5rem; }
.setup-card h3 { margin-bottom:1rem; display:flex; align-items:center; gap:.5rem; }
.cta-row { display:flex; gap:1rem; margin-top:2rem; flex-wrap:wrap; }
.count-banner {
background: var(--primary-l); border:1px solid #bfdbfe;
border-radius:var(--radius-sm); padding:.65rem 1rem;
font-size:.875rem; font-weight:600; color:var(--primary);
display:flex; align-items:center; gap:.5rem; margin-top:1.25rem;
}
.subject-hero-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:1rem; margin-top:2rem; }
.subj-card {
border-radius:var(--radius); padding:1.25rem 1rem; cursor:pointer;
border:2px solid transparent; transition:var(--transition); text-align:center;
}
.subj-card .icon { font-size:2rem; margin-bottom:.5rem; }
.subj-card h4 { font-size:.9rem; font-weight:700; }
.subj-card p { font-size:.75rem; margin-top:.2rem; }
.subj-card.anatomy { background:#dbeafe; }
.subj-card.physiology { background:#dcfce7; }
.subj-card.biochemistry { background:#ede9fe; }
.subj-card.selected, .subj-card:hover { border-color: var(--primary); box-shadow:var(--shadow); transform:translateY(-2px); }
</style>
</head>
<body>
<!-- Navbar -->
<nav class="navbar">
<a class="navbar-brand" href="index.html">
<div class="logo-icon">K</div>
KUHS QBank <span class="sub">1st Year MBBS</span>
</a>
<div class="navbar-links">
<a href="index.html" class="active">Home</a>
<a href="browse.html">Browse</a>
<a href="flashcard.html">Flashcards</a>
<a href="analysis.html">Analysis</a>
</div>
</nav>
<!-- Hero -->
<div class="hero">
<div class="container">
<h1>KUHS 1st Year MBBS<br>Question Bank</h1>
<p>Previous year questions from Kerala University of Health Sciences</p>
<div class="badge-wrap">
<span class="hero-badge">📚 Anatomy</span>
<span class="hero-badge">🫀 Physiology</span>
<span class="hero-badge">🧬 Biochemistry</span>
<span class="hero-badge" id="hero-total">— Questions</span>
</div>
</div>
</div>
<!-- Main -->
<main class="page-wrap">
<div class="container">
<div class="setup-grid">
<!-- Subject selector -->
<div class="setup-card">
<h3>📖 Choose Subjects</h3>
<div class="subject-hero-cards" id="subjectCards">
<div class="subj-card anatomy selected" data-subj="Anatomy">
<div class="icon">🦴</div>
<h4>Anatomy</h4>
<p class="q-count" id="cnt-Anatomy">0 Qs</p>
</div>
<div class="subj-card physiology selected" data-subj="Physiology">
<div class="icon">🫀</div>
<h4>Physiology</h4>
<p class="q-count" id="cnt-Physiology">0 Qs</p>
</div>
<div class="subj-card biochemistry selected" data-subj="Biochemistry">
<div class="icon">🧬</div>
<h4>Biochemistry</h4>
<p class="q-count" id="cnt-Biochemistry">0 Qs</p>
</div>
</div>
</div>
<!-- Filter + mode -->
<div class="setup-card">
<h3>⚙️ Study Options</h3>
<p style="font-size:.8rem;color:var(--text-muted);margin-bottom:.75rem;">Question type</p>
<div class="tag-group" id="typeTags">
<span class="tag selected" data-type="LAQ">LAQ</span>
<span class="tag selected" data-type="SAQ">SAQ</span>
<span class="tag selected" data-type="MCQ">MCQ</span>
</div>
<p style="font-size:.8rem;color:var(--text-muted);margin-bottom:.75rem;margin-top:1.25rem;">Exam year</p>
<div class="tag-group" id="yearTags"></div>
<p style="font-size:.8rem;color:var(--text-muted);margin-bottom:.75rem;margin-top:1.25rem;">Study mode</p>
<div class="mode-toggle">
<button class="mode-btn active" id="modeFlash">🃏 Flashcards</button>
<button class="mode-btn" id="modeBrowse">📋 Browse All</button>
</div>
<div class="count-banner" id="countBanner">
<span>✅</span> <span id="selCount">0</span> questions selected
</div>
<div class="cta-row">
<button class="btn btn-primary btn-lg" id="startBtn">Start Studying →</button>
<a href="browse.html" class="btn btn-ghost btn-lg">Browse First</a>
</div>
</div>
</div>
</div>
</main>
<footer class="footer">
KUHS 1st Year MBBS Question Bank · Built with ❤️ for medical students
</footer>
<script src="data.js"></script>
<script>
// ── Subject card toggling ────────────────────────────────
const selectedSubjects = new Set(SUBJECTS);
const selectedTypes = new Set(TYPES);
const selectedYears = new Set(YEARS);
let studyMode = 'flashcard';
// populate counts
document.getElementById('hero-total').textContent = QUESTIONS.length + ' Questions';
SUBJECTS.forEach(s => {
const el = document.getElementById('cnt-' + s);
if (el) el.textContent = QUESTIONS.filter(q => q.subject === s).length + ' Qs';
});
// Year tags
const yearTagContainer = document.getElementById('yearTags');
YEARS.forEach(y => {
const span = document.createElement('span');
span.className = 'tag selected'; span.dataset.year = y; span.textContent = y;
yearTagContainer.appendChild(span);
});
function updateCount() {
const n = filterQuestions({
subjects: [...selectedSubjects],
types : [...selectedTypes],
years : [...selectedYears]
}).length;
document.getElementById('selCount').textContent = n;
}
updateCount();
// Subject cards
document.getElementById('subjectCards').addEventListener('click', e => {
const card = e.target.closest('[data-subj]');
if (!card) return;
const s = card.dataset.subj;
if (selectedSubjects.has(s)) { selectedSubjects.delete(s); card.classList.remove('selected'); }
else { selectedSubjects.add(s); card.classList.add('selected'); }
updateCount();
});
// Type tags
document.getElementById('typeTags').addEventListener('click', e => {
const tag = e.target.closest('[data-type]');
if (!tag) return;
const t = tag.dataset.type;
if (selectedTypes.has(t)) { selectedTypes.delete(t); tag.classList.remove('selected'); }
else { selectedTypes.add(t); tag.classList.add('selected'); }
updateCount();
});
// Year tags
yearTagContainer.addEventListener('click', e => {
const tag = e.target.closest('[data-year]');
if (!tag) return;
const y = tag.dataset.year;
if (selectedYears.has(y)) { selectedYears.delete(y); tag.classList.remove('selected'); }
else { selectedYears.add(y); tag.classList.add('selected'); }
updateCount();
});
// Mode toggle
document.getElementById('modeFlash').addEventListener('click', () => {
studyMode = 'flashcard';
document.getElementById('modeFlash').classList.add('active');
document.getElementById('modeBrowse').classList.remove('active');
});
document.getElementById('modeBrowse').addEventListener('click', () => {
studyMode = 'browse';
document.getElementById('modeBrowse').classList.add('active');
document.getElementById('modeFlash').classList.remove('active');
});
// Start button
document.getElementById('startBtn').addEventListener('click', () => {
const params = new URLSearchParams({
subjects: [...selectedSubjects].join(','),
types : [...selectedTypes].join(','),
years : [...selectedYears].join(',')
});
if (studyMode === 'flashcard') {
window.location.href = 'flashcard.html?' + params;
} else {
window.location.href = 'browse.html?' + params;
}
});
</script>
</body>
</html>
~/kuhs-qbank/flashcard.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Flashcards — KUHS QBank</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
<style>
.fc-layout { display:grid; grid-template-columns:300px 1fr; gap:1.5rem; align-items:start; }
@media(max-width:860px){ .fc-layout { grid-template-columns:1fr; } }
.fc-sidebar { position:sticky; top:80px; }
.sidebar-section { margin-bottom:1.25rem; }
.sidebar-section h4 { font-size:.8rem; font-weight:700; color:var(--text-muted); text-transform:uppercase; letter-spacing:.08em; margin-bottom:.5rem; }
.fc-main {}
.fc-topbar { display:flex; align-items:center; justify-content:space-between; margin-bottom:1.25rem; flex-wrap:wrap; gap:.75rem; }
.fc-counter { font-size:.875rem; font-weight:600; color:var(--text-muted); }
.fc-controls { display:flex; gap:.5rem; flex-wrap:wrap; }
/* keyboard hint */
.key-hint { font-size:.72rem; color:var(--text-light); margin-top:.5rem; text-align:center; }
kbd { background:var(--border); border-radius:4px; padding:.1rem .35rem; font-size:.7rem; }
/* confidence buttons */
.conf-row { display:flex; gap:.75rem; justify-content:center; margin-top:1.25rem; flex-wrap:wrap; }
.conf-btn {
padding:.55rem 1.25rem; border-radius:var(--radius-sm);
font-size:.875rem; font-weight:700; cursor:pointer;
border:2px solid transparent; transition:var(--transition);
}
.conf-easy { background:var(--success-l); color:var(--success); border-color:var(--success); }
.conf-hard { background:var(--warn-l); color:var(--warn); border-color:var(--warn); }
.conf-again { background:var(--danger-l); color:var(--danger); border-color:var(--danger); }
.conf-easy:hover { background:var(--success); color:#fff; }
.conf-hard:hover { background:var(--warn); color:#fff; }
.conf-again:hover { background:var(--danger); color:#fff; }
.conf-row { display:none; }
.conf-row.visible { display:flex; }
/* Score summary */
.score-panel {
text-align:center; padding:2rem;
background:var(--surface); border:1px solid var(--border);
border-radius:var(--radius); box-shadow:var(--shadow);
display:none;
}
.score-panel.visible { display:block; }
.score-big { font-size:4rem; font-weight:800; color:var(--primary); }
.score-row { display:flex; gap:1rem; justify-content:center; margin:1rem 0; flex-wrap:wrap; }
.score-pill {
padding:.4rem 1rem; border-radius:999px; font-size:.875rem; font-weight:600;
}
.pill-easy { background:var(--success-l); color:var(--success); }
.pill-hard { background:var(--warn-l); color:var(--warn); }
.pill-again { background:var(--danger-l); color:var(--danger); }
/* MCQ on flashcard */
.fc-mcq-opts { width:100%; text-align:left; margin-top:1.25rem; }
.fc-mcq-opts li {
padding:.6rem 1rem; border-radius:var(--radius-sm);
border:2px solid var(--border); margin-bottom:.5rem;
cursor:pointer; transition:var(--transition); font-size:.875rem;
}
.fc-mcq-opts li:hover:not(.locked) { border-color:var(--primary); background:var(--primary-l); }
.fc-mcq-opts li.correct { border-color:var(--success); background:var(--success-l); color:var(--success); font-weight:600; }
.fc-mcq-opts li.wrong { border-color:var(--danger); background:var(--danger-l); color:var(--danger); }
</style>
</head>
<body>
<!-- Navbar -->
<nav class="navbar">
<a class="navbar-brand" href="index.html">
<div class="logo-icon">K</div>
KUHS QBank <span class="sub">1st Year MBBS</span>
</a>
<div class="navbar-links">
<a href="index.html">Home</a>
<a href="browse.html">Browse</a>
<a href="flashcard.html" class="active">Flashcards</a>
<a href="analysis.html">Analysis</a>
</div>
</nav>
<main class="page-wrap">
<div class="container">
<div class="fc-layout">
<!-- Sidebar -->
<aside class="fc-sidebar">
<div class="card" style="padding:1.25rem">
<div class="sidebar-section">
<h4>Session Progress</h4>
<div class="progress-wrap" style="margin-bottom:.5rem">
<div class="progress-bar" id="progressBar" style="width:0%"></div>
</div>
<div style="font-size:.8rem;color:var(--text-muted)" id="progressText">0 / 0 done</div>
</div>
<div class="sidebar-section">
<h4>Results so far</h4>
<div style="display:flex;flex-direction:column;gap:.5rem" id="sideResults">
<div style="display:flex;justify-content:space-between;font-size:.85rem">
<span style="color:var(--success);font-weight:600">✅ Easy</span><span id="cntEasy">0</span>
</div>
<div style="display:flex;justify-content:space-between;font-size:.85rem">
<span style="color:var(--warn);font-weight:600">⚠️ Hard</span><span id="cntHard">0</span>
</div>
<div style="display:flex;justify-content:space-between;font-size:.85rem">
<span style="color:var(--danger);font-weight:600">🔁 Again</span><span id="cntAgain">0</span>
</div>
</div>
</div>
<div class="sidebar-section">
<h4>Current Card</h4>
<div id="sideSubject" style="font-size:.8rem;color:var(--text-muted)">—</div>
<div id="sideTopic" style="font-size:.75rem;color:var(--text-light);margin-top:.2rem">—</div>
</div>
<div style="display:flex;flex-direction:column;gap:.5rem;margin-top:.5rem">
<button class="btn btn-ghost btn-sm" id="shuffleBtn">🔀 Shuffle</button>
<a href="index.html" class="btn btn-ghost btn-sm">← Back to Setup</a>
</div>
</div>
</aside>
<!-- Main flashcard area -->
<div class="fc-main">
<div class="fc-topbar">
<span class="fc-counter" id="fcCounter">Card 1 of —</span>
<div class="fc-controls">
<button class="btn btn-ghost btn-sm" id="prevBtn">← Prev</button>
<button class="btn btn-ghost btn-sm" id="nextBtn">Next →</button>
</div>
</div>
<!-- The flashcard -->
<div class="flashcard-scene" id="flashcardScene">
<div class="flashcard" id="flashcard">
<div class="flashcard-face flashcard-front" id="cardFront">
<div id="frontMeta" style="display:flex;gap:.4rem;flex-wrap:wrap;margin-bottom:1rem;justify-content:center"></div>
<p class="flashcard-q" id="frontQ">Loading…</p>
<ul class="fc-mcq-opts" id="mcqOpts" style="display:none"></ul>
<span class="flashcard-hint" id="flipHint">Click to reveal answer</span>
</div>
<div class="flashcard-face flashcard-back" id="cardBack">
<div id="backMeta" style="display:flex;gap:.4rem;flex-wrap:wrap;margin-bottom:1rem;justify-content:center"></div>
<p class="flashcard-a" id="backA">Answer goes here</p>
<span class="flashcard-hint">Click to go back</span>
</div>
</div>
</div>
<p class="key-hint"><kbd>Space</kbd> flip <kbd>←</kbd> prev <kbd>→</kbd> next <kbd>1</kbd> Easy <kbd>2</kbd> Hard <kbd>3</kbd> Again</p>
<!-- Confidence buttons (shown after flip) -->
<div class="conf-row" id="confRow">
<button class="conf-btn conf-easy" id="btnEasy">✅ Easy</button>
<button class="conf-btn conf-hard" id="btnHard">⚠️ Hard</button>
<button class="conf-btn conf-again" id="btnAgain">🔁 Again</button>
</div>
<!-- Score panel (end of deck) -->
<div class="score-panel" id="scorePanel">
<div style="font-size:2rem;margin-bottom:.5rem">🎉</div>
<p style="font-weight:700;font-size:1.25rem;color:var(--text);margin-bottom:.25rem">Session Complete!</p>
<div class="score-big" id="scorePct">0%</div>
<p style="margin-bottom:1rem">cards marked easy</p>
<div class="score-row">
<span class="score-pill pill-easy" id="sFinal-easy">Easy: 0</span>
<span class="score-pill pill-hard" id="sFinal-hard">Hard: 0</span>
<span class="score-pill pill-again" id="sFinal-again">Again: 0</span>
</div>
<div style="display:flex;gap:1rem;justify-content:center;margin-top:1rem;flex-wrap:wrap">
<button class="btn btn-primary" id="restartBtn">🔁 Restart Session</button>
<a href="index.html" class="btn btn-ghost">← Home</a>
</div>
</div>
</div><!-- /fc-main -->
</div>
</div>
</main>
<footer class="footer">
KUHS 1st Year MBBS Question Bank · Built with ❤️ for medical students
</footer>
<script src="data.js"></script>
<script>
(function() {
// ── Parse URL params ──────────────────────────────────
const params = new URLSearchParams(location.search);
const subjects = params.get('subjects') ? params.get('subjects').split(',') : SUBJECTS;
const types = params.get('types') ? params.get('types').split(',') : TYPES;
const years = params.get('years') ? params.get('years').split(',') : YEARS;
let deck = shuffle(filterQuestions({ subjects, types, years }));
let idx = 0;
let flipped = false;
const results = { easy: 0, hard: 0, again: 0 };
const answered = {}; // idx → 'easy'|'hard'|'again'
let mcqAnswered = false;
// ── Helpers ───────────────────────────────────────────
function badgeHTML(q) {
return `<span class="badge badge-${q.subject.toLowerCase()}">${q.subject}</span>
<span class="badge badge-${q.type.toLowerCase()}">${q.type}</span>
<span class="badge" style="background:var(--bg);color:var(--text-muted)">${q.year}</span>`;
}
function renderCard() {
if (deck.length === 0) { showEmpty(); return; }
const q = deck[idx];
flipped = false;
mcqAnswered = false;
document.getElementById('flashcard').classList.remove('flipped');
document.getElementById('frontMeta').innerHTML = badgeHTML(q);
document.getElementById('backMeta').innerHTML = badgeHTML(q);
document.getElementById('frontQ').textContent = q.question;
document.getElementById('backA').textContent = q.answer || '—';
document.getElementById('fcCounter').textContent = `Card ${idx + 1} of ${deck.length}`;
document.getElementById('sideSubject').textContent = q.subject;
document.getElementById('sideTopic').textContent = q.topic || '';
document.getElementById('confRow').classList.remove('visible');
// MCQ
const mcqOpts = document.getElementById('mcqOpts');
const flipHint = document.getElementById('flipHint');
if (q.type === 'MCQ' && q.options) {
mcqOpts.innerHTML = '';
mcqOpts.style.display = 'block';
flipHint.textContent = 'Select an answer';
q.options.forEach((opt, i) => {
const li = document.createElement('li');
li.textContent = String.fromCharCode(65 + i) + '. ' + opt;
li.dataset.idx = i;
li.addEventListener('click', () => handleMCQ(li, i, q));
mcqOpts.appendChild(li);
});
} else {
mcqOpts.style.display = 'none';
flipHint.textContent = 'Click to reveal answer';
}
// progress
const done = Object.keys(answered).length;
document.getElementById('progressText').textContent = `${done} / ${deck.length} done`;
document.getElementById('progressBar').style.width = `${(done/deck.length)*100}%`;
document.getElementById('prevBtn').disabled = idx === 0;
document.getElementById('nextBtn').disabled = idx === deck.length - 1;
}
function handleMCQ(li, chosen, q) {
if (mcqAnswered) return;
mcqAnswered = true;
const items = li.parentElement.querySelectorAll('li');
items.forEach(el => el.classList.add('locked'));
items[q.correct].classList.add('correct');
if (chosen !== q.correct) li.classList.add('wrong');
document.getElementById('flipHint').textContent = 'Click to see full answer';
document.getElementById('confRow').classList.add('visible');
}
function flipCard() {
if (!flipped) {
document.getElementById('flashcard').classList.add('flipped');
flipped = true;
const q = deck[idx];
if (q.type !== 'MCQ') {
document.getElementById('confRow').classList.add('visible');
}
} else {
document.getElementById('flashcard').classList.remove('flipped');
flipped = false;
}
}
function markResult(conf) {
if (answered[idx]) {
results[answered[idx]]--;
}
results[conf]++;
answered[idx] = conf;
document.getElementById('cntEasy').textContent = results.easy;
document.getElementById('cntHard').textContent = results.hard;
document.getElementById('cntAgain').textContent = results.again;
// progress
const done = Object.keys(answered).length;
document.getElementById('progressText').textContent = `${done} / ${deck.length} done`;
document.getElementById('progressBar').style.width = `${(done/deck.length)*100}%`;
if (idx < deck.length - 1) {
idx++;
renderCard();
} else {
showScore();
}
}
function showScore() {
document.getElementById('flashcardScene').style.display = 'none';
document.getElementById('confRow').style.display = 'none';
document.querySelector('.key-hint').style.display = 'none';
document.querySelector('.fc-topbar').style.display = 'none';
const pct = deck.length ? Math.round((results.easy / deck.length) * 100) : 0;
document.getElementById('scorePct').textContent = pct + '%';
document.getElementById('sFinal-easy').textContent = 'Easy: ' + results.easy;
document.getElementById('sFinal-hard').textContent = 'Hard: ' + results.hard;
document.getElementById('sFinal-again').textContent = 'Again: ' + results.again;
document.getElementById('scorePanel').classList.add('visible');
}
function showEmpty() {
document.getElementById('fcCounter').textContent = 'No questions match your filters';
document.getElementById('flashcardScene').innerHTML =
'<div class="empty-state"><div class="icon">🔍</div><p>No questions match your filters.</p><a href="index.html" class="btn btn-primary" style="margin-top:1rem">← Change Filters</a></div>';
}
function restart() {
deck = shuffle(filterQuestions({ subjects, types, years }));
idx = 0;
Object.keys(results).forEach(k => results[k] = 0);
Object.keys(answered).forEach(k => delete answered[k]);
document.getElementById('cntEasy').textContent = 0;
document.getElementById('cntHard').textContent = 0;
document.getElementById('cntAgain').textContent = 0;
document.getElementById('scorePanel').classList.remove('visible');
document.getElementById('flashcardScene').style.display = '';
document.getElementById('confRow').style.display = '';
document.querySelector('.key-hint').style.display = '';
document.querySelector('.fc-topbar').style.display = '';
renderCard();
}
// ── Events ────────────────────────────────────────────
document.getElementById('flashcard').addEventListener('click', flipCard);
document.getElementById('nextBtn').addEventListener('click', () => { if (idx < deck.length - 1) { idx++; renderCard(); } });
document.getElementById('prevBtn').addEventListener('click', () => { if (idx > 0) { idx--; renderCard(); } });
document.getElementById('btnEasy').addEventListener('click', () => markResult('easy'));
document.getElementById('btnHard').addEventListener('click', () => markResult('hard'));
document.getElementById('btnAgain').addEventListener('click', () => markResult('again'));
document.getElementById('shuffleBtn').addEventListener('click', () => { deck = shuffle(deck); idx = 0; renderCard(); });
document.getElementById('restartBtn').addEventListener('click', restart);
// Keyboard shortcuts
document.addEventListener('keydown', e => {
if (e.key === ' ') { e.preventDefault(); flipCard(); }
if (e.key === 'ArrowRight') { if (idx < deck.length - 1) { idx++; renderCard(); } }
if (e.key === 'ArrowLeft') { if (idx > 0) { idx--; renderCard(); } }
if (e.key === '1') document.getElementById('btnEasy').click();
if (e.key === '2') document.getElementById('btnHard').click();
if (e.key === '3') document.getElementById('btnAgain').click();
});
// Init
renderCard();
})();
</script>
</body>
</html>
~/kuhs-qbank/browse.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Browse Questions — KUHS QBank</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
<style>
.q-number {
width:28px; height:28px; border-radius:50%;
background:var(--primary-l); color:var(--primary);
font-size:.75rem; font-weight:800;
display:flex; align-items:center; justify-content:center;
flex-shrink:0;
}
.subject-group { margin-bottom:2.5rem; }
.group-header {
display:flex; align-items:center; justify-content:space-between;
padding:.75rem 0; border-bottom:2px solid var(--border);
margin-bottom:1rem;
}
.group-header h3 { display:flex; align-items:center; gap:.5rem; }
.expand-all-btn { font-size:.75rem; color:var(--primary); font-weight:600; cursor:pointer; }
.expand-all-btn:hover { text-decoration:underline; }
.mcq-answer-opts { margin-top:.5rem; }
.mcq-answer-opts li {
padding:.4rem .75rem; border-radius:var(--radius-sm);
border:2px solid var(--border); margin-bottom:.35rem;
font-size:.85rem; list-style:none;
}
.mcq-answer-opts li.correct { border-color:var(--success); background:var(--success-l); color:var(--success); font-weight:600; }
.no-results { text-align:center; padding:3rem 1rem; color:var(--text-muted); }
</style>
</head>
<body>
<!-- Navbar -->
<nav class="navbar">
<a class="navbar-brand" href="index.html">
<div class="logo-icon">K</div>
KUHS QBank <span class="sub">1st Year MBBS</span>
</a>
<div class="navbar-links">
<a href="index.html">Home</a>
<a href="browse.html" class="active">Browse</a>
<a href="flashcard.html">Flashcards</a>
<a href="analysis.html">Analysis</a>
</div>
</nav>
<main class="page-wrap">
<div class="container">
<div style="display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:1rem;margin-bottom:1.5rem">
<div>
<h2>Browse Questions</h2>
<p style="margin-top:.25rem">Click any question to reveal the answer</p>
</div>
<a href="flashcard.html" class="btn btn-primary">🃏 Switch to Flashcards</a>
</div>
<!-- Filter bar -->
<div class="filter-bar">
<input type="text" class="search-input" id="searchInput" placeholder="🔍 Search questions…" />
<select class="filter-select" id="subjectFilter">
<option value="">All Subjects</option>
<option value="Anatomy">Anatomy</option>
<option value="Physiology">Physiology</option>
<option value="Biochemistry">Biochemistry</option>
</select>
<select class="filter-select" id="typeFilter">
<option value="">All Types</option>
<option value="LAQ">LAQ</option>
<option value="SAQ">SAQ</option>
<option value="MCQ">MCQ</option>
</select>
<select class="filter-select" id="yearFilter">
<option value="">All Years</option>
</select>
<button class="btn btn-ghost btn-sm" id="clearFilters">✕ Clear</button>
</div>
<!-- Results count -->
<div class="section-title" id="resultsTitle">
All Questions <span class="count" id="resultsCount">0</span>
</div>
<!-- Question list -->
<div id="questionList"></div>
</div>
</main>
<footer class="footer">
KUHS 1st Year MBBS Question Bank · Built with ❤️ for medical students
</footer>
<script src="data.js"></script>
<script>
(function() {
// Populate year filter
const yearFilter = document.getElementById('yearFilter');
YEARS.forEach(y => {
const opt = document.createElement('option');
opt.value = y; opt.textContent = y;
yearFilter.appendChild(opt);
});
// Read URL params for initial filter
const params = new URLSearchParams(location.search);
if (params.get('subjects')) document.getElementById('subjectFilter').value = params.get('subjects').split(',')[0] || '';
let filteredQs = [];
function getFilters() {
return {
search : document.getElementById('searchInput').value.toLowerCase().trim(),
subject : document.getElementById('subjectFilter').value,
type : document.getElementById('typeFilter').value,
year : document.getElementById('yearFilter').value
};
}
function applyFilters() {
const f = getFilters();
filteredQs = QUESTIONS.filter(q => {
if (f.subject && q.subject !== f.subject) return false;
if (f.type && q.type !== f.type) return false;
if (f.year && q.year !== f.year) return false;
if (f.search && !q.question.toLowerCase().includes(f.search) &&
!(q.answer || '').toLowerCase().includes(f.search) &&
!(q.topic || '').toLowerCase().includes(f.search)) return false;
return true;
});
render();
}
function subjectIcon(s) {
return { Anatomy: '🦴', Physiology: '🫀', Biochemistry: '🧬' }[s] || '📚';
}
function subjectColor(s) {
return { Anatomy: '#2563eb', Physiology: '#16a34a', Biochemistry: '#7c3aed' }[s] || '#64748b';
}
function render() {
const list = document.getElementById('questionList');
document.getElementById('resultsCount').textContent = filteredQs.length;
if (filteredQs.length === 0) {
list.innerHTML = '<div class="no-results"><div style="font-size:3rem">🔍</div><p>No questions match your search.</p></div>';
return;
}
// Group by subject
const groups = {};
filteredQs.forEach(q => {
if (!groups[q.subject]) groups[q.subject] = [];
groups[q.subject].push(q);
});
list.innerHTML = '';
Object.entries(groups).forEach(([subj, qs]) => {
const section = document.createElement('div');
section.className = 'subject-group';
// Group header
section.innerHTML = `
<div class="group-header">
<h3>${subjectIcon(subj)} ${subj} <span class="badge" style="background:var(--border);color:var(--text-muted)">${qs.length}</span></h3>
<span class="expand-all-btn" data-group="${subj}">Expand All</span>
</div>
`;
// Questions
qs.forEach((q, i) => {
const item = document.createElement('div');
item.className = 'q-item';
item.dataset.id = q.id;
// Build answer HTML
let answerHTML = '';
if (q.type === 'MCQ' && q.options) {
const opts = q.options.map((opt, oi) =>
`<li class="${oi === q.correct ? 'correct' : ''}">${String.fromCharCode(65+oi)}. ${opt}</li>`
).join('');
answerHTML = `<ul class="mcq-answer-opts">${opts}</ul>`;
if (q.answer) answerHTML += `<p style="margin-top:.75rem;font-size:.85rem;color:var(--text)">${q.answer}</p>`;
} else {
answerHTML = `<p style="font-size:.875rem;line-height:1.7;color:var(--text)">${q.answer || 'Answer not available.'}</p>`;
}
item.innerHTML = `
<div class="q-item-header">
<div style="flex:1">
<div class="q-item-meta">
<span class="q-number">${i + 1}</span>
<span class="badge badge-${q.subject.toLowerCase()}">${q.subject}</span>
<span class="badge badge-${q.type.toLowerCase()}">${q.type}</span>
<span class="badge" style="background:var(--bg);color:var(--text-muted);border:1px solid var(--border)">${q.year}</span>
${q.topic ? `<span style="font-size:.75rem;color:var(--text-light)">${q.topic}</span>` : ''}
</div>
<div class="q-item-body">${q.question}</div>
</div>
<span class="toggle-ans" data-id="${q.id}">Show Answer</span>
</div>
<div class="q-item-answer" id="ans-${q.id}">
${answerHTML}
</div>
`;
section.appendChild(item);
});
list.appendChild(section);
});
// Toggle answer
list.querySelectorAll('.toggle-ans').forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.dataset.id;
const ans = document.getElementById('ans-' + id);
if (ans.classList.contains('open')) {
ans.classList.remove('open'); btn.textContent = 'Show Answer';
} else {
ans.classList.add('open'); btn.textContent = 'Hide Answer';
}
});
});
// Expand all per group
list.querySelectorAll('.expand-all-btn').forEach(btn => {
btn.addEventListener('click', () => {
const grp = btn.dataset.group;
const allAns = list.querySelectorAll(`[id^="ans-"]`);
const items = [...allAns].filter(el => {
const parentItem = el.closest('.subject-group');
return parentItem && parentItem.querySelector('.group-header h3')?.textContent.includes(grp);
});
const allOpen = items.every(el => el.classList.contains('open'));
items.forEach(el => {
const id = el.id.replace('ans-', '');
const tog = el.parentElement.querySelector(`.toggle-ans[data-id="${id}"]`);
if (allOpen) { el.classList.remove('open'); if(tog) tog.textContent='Show Answer'; }
else { el.classList.add('open'); if(tog) tog.textContent='Hide Answer'; }
});
btn.textContent = allOpen ? 'Expand All' : 'Collapse All';
});
});
}
// Event listeners
document.getElementById('searchInput').addEventListener('input', applyFilters);
document.getElementById('subjectFilter').addEventListener('change', applyFilters);
document.getElementById('typeFilter').addEventListener('change', applyFilters);
document.getElementById('yearFilter').addEventListener('change', applyFilters);
document.getElementById('clearFilters').addEventListener('click', () => {
document.getElementById('searchInput').value = '';
document.getElementById('subjectFilter').value = '';
document.getElementById('typeFilter').value = '';
document.getElementById('yearFilter').value = '';
applyFilters();
});
applyFilters();
})();
</script>
</body>
</html>
~/kuhs-qbank/analysis.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Analysis — KUHS QBank</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
<style>
.analysis-grid { display:grid; grid-template-columns:1fr 1fr; gap:1.5rem; }
@media(max-width:700px){ .analysis-grid { grid-template-columns:1fr; } }
.card-title { font-size:1rem; font-weight:700; margin-bottom:1.25rem; display:flex; align-items:center; gap:.5rem; }
/* Donut chart */
.donut-container { position:relative; width:180px; height:180px; margin:0 auto 1.25rem; }
.donut-container canvas { width:180px!important; height:180px!important; }
.donut-center {
position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
text-align:center; pointer-events:none;
}
.donut-center .val { font-size:1.75rem; font-weight:800; color:var(--text); }
.donut-center .lbl { font-size:.7rem; color:var(--text-muted); font-weight:600; }
/* Legend */
.legend { display:flex; flex-direction:column; gap:.5rem; }
.legend-item { display:flex; align-items:center; gap:.6rem; font-size:.85rem; }
.legend-dot { width:12px; height:12px; border-radius:50%; flex-shrink:0; }
/* Year heatmap */
.year-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(80px,1fr)); gap:.75rem; }
.year-tile {
border-radius:var(--radius-sm); padding:.875rem .5rem; text-align:center;
transition:var(--transition); cursor:default;
}
.year-tile .year-num { font-size:.8rem; font-weight:700; color:var(--text-muted); }
.year-tile .year-cnt { font-size:1.5rem; font-weight:800; color:var(--text); }
/* Topic table */
.topic-table { width:100%; border-collapse:collapse; font-size:.875rem; }
.topic-table th {
text-align:left; padding:.5rem .75rem;
border-bottom:2px solid var(--border);
font-size:.75rem; font-weight:700; color:var(--text-muted); text-transform:uppercase;
}
.topic-table td { padding:.55rem .75rem; border-bottom:1px solid var(--border); }
.topic-table tr:last-child td { border-bottom:none; }
.topic-table tr:hover td { background:var(--bg); }
.mini-bar { height:8px; border-radius:999px; display:inline-block; min-width:4px; }
</style>
</head>
<body>
<!-- Navbar -->
<nav class="navbar">
<a class="navbar-brand" href="index.html">
<div class="logo-icon">K</div>
KUHS QBank <span class="sub">1st Year MBBS</span>
</a>
<div class="navbar-links">
<a href="index.html">Home</a>
<a href="browse.html">Browse</a>
<a href="flashcard.html">Flashcards</a>
<a href="analysis.html" class="active">Analysis</a>
</div>
</nav>
<main class="page-wrap">
<div class="container">
<div style="margin-bottom:2rem">
<h2>Question Bank Analysis</h2>
<p style="margin-top:.25rem">Breakdown of KUHS 1st Year previous year questions by subject, type, topic and year.</p>
</div>
<!-- Top stat cards -->
<div class="stat-grid" style="margin-bottom:1.75rem" id="topStats"></div>
<!-- Row 1 -->
<div class="analysis-grid" style="margin-bottom:1.5rem">
<!-- Subject donut -->
<div class="card">
<div class="card-title">📚 Questions by Subject</div>
<div style="display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;justify-content:center">
<div class="donut-container">
<canvas id="subjectDonut"></canvas>
<div class="donut-center">
<div class="val" id="totalDot"></div>
<div class="lbl">Total</div>
</div>
</div>
<div class="legend" id="subjectLegend"></div>
</div>
</div>
<!-- Type donut -->
<div class="card">
<div class="card-title">📝 Questions by Type</div>
<div style="display:flex;align-items:center;gap:1.5rem;flex-wrap:wrap;justify-content:center">
<div class="donut-container">
<canvas id="typeDonut"></canvas>
<div class="donut-center">
<div class="val" id="typeDot"></div>
<div class="lbl">Total</div>
</div>
</div>
<div class="legend" id="typeLegend"></div>
</div>
</div>
</div>
<!-- Row 2 -->
<div class="analysis-grid" style="margin-bottom:1.5rem">
<!-- Subject breakdown bars -->
<div class="card">
<div class="card-title">🔬 Subject × Type Breakdown</div>
<div id="subjectTypeBars"></div>
</div>
<!-- Year tiles -->
<div class="card">
<div class="card-title">📅 Questions by Year</div>
<div class="year-grid" id="yearGrid"></div>
</div>
</div>
<!-- Full width — top topics -->
<div class="card">
<div class="card-title">🗂️ Questions by Topic</div>
<table class="topic-table" id="topicTable">
<thead>
<tr>
<th>Topic</th>
<th>Subject</th>
<th>Count</th>
<th style="width:40%">Distribution</th>
</tr>
</thead>
<tbody id="topicBody"></tbody>
</table>
</div>
</div>
</main>
<footer class="footer">
KUHS 1st Year MBBS Question Bank · Built with ❤️ for medical students
</footer>
<script src="data.js"></script>
<script>
// ── Pure-canvas donut chart (no external libraries) ──────
function drawDonut(canvasId, segments) {
const canvas = document.getElementById(canvasId);
if (!canvas) return;
const ctx = canvas.getContext('2d');
const W = 180, H = 180;
canvas.width = W; canvas.height = H;
const cx = W/2, cy = H/2, R = 78, inner = 52;
let start = -Math.PI / 2;
const total = segments.reduce((s, d) => s + d.value, 0);
segments.forEach(seg => {
const sweep = (seg.value / total) * 2 * Math.PI;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, R, start, start + sweep);
ctx.closePath();
ctx.fillStyle = seg.color;
ctx.fill();
start += sweep;
});
// Cutout
ctx.beginPath();
ctx.arc(cx, cy, inner, 0, 2 * Math.PI);
ctx.fillStyle = '#fff';
ctx.fill();
}
(function() {
const total = QUESTIONS.length;
document.getElementById('totalDot').textContent = total;
document.getElementById('typeDot').textContent = total;
// ── Top stat cards ──────────────────────────────────
const statData = [
{ label: 'Total Questions', val: total, icon: '📚' },
{ label: 'Subjects', val: SUBJECTS.length, icon: '🔬' },
{ label: 'Exam Years', val: YEARS.length, icon: '📅' },
{ label: 'Topics Covered', val: [...new Set(QUESTIONS.map(q=>q.topic))].filter(Boolean).length, icon: '🗂️' }
];
const statGrid = document.getElementById('topStats');
statData.forEach(s => {
statGrid.innerHTML += `
<div class="stat-card">
<div style="font-size:1.5rem;margin-bottom:.25rem">${s.icon}</div>
<div class="stat-value">${s.val}</div>
<div class="stat-label">${s.label}</div>
</div>`;
});
// ── Subject donut ───────────────────────────────────
const SUBJ_COLORS = { Anatomy: '#2563eb', Physiology: '#16a34a', Biochemistry: '#7c3aed' };
const subjCounts = {};
SUBJECTS.forEach(s => subjCounts[s] = QUESTIONS.filter(q => q.subject === s).length);
const subjSegs = SUBJECTS.map(s => ({ value: subjCounts[s], color: SUBJ_COLORS[s] || '#94a3b8' }));
drawDonut('subjectDonut', subjSegs);
const subjLegend = document.getElementById('subjectLegend');
SUBJECTS.forEach(s => {
subjLegend.innerHTML += `
<div class="legend-item">
<div class="legend-dot" style="background:${SUBJ_COLORS[s]}"></div>
<span><strong>${s}</strong> — ${subjCounts[s]}</span>
</div>`;
});
// ── Type donut ──────────────────────────────────────
const TYPE_COLORS = { LAQ: '#d97706', SAQ: '#0ea5e9', MCQ: '#ec4899' };
const typeCounts = {};
TYPES.forEach(t => typeCounts[t] = QUESTIONS.filter(q => q.type === t).length);
const typeSegs = TYPES.map(t => ({ value: typeCounts[t] || 0, color: TYPE_COLORS[t] || '#94a3b8' }));
drawDonut('typeDonut', typeSegs);
const typeLegend = document.getElementById('typeLegend');
TYPES.forEach(t => {
typeLegend.innerHTML += `
<div class="legend-item">
<div class="legend-dot" style="background:${TYPE_COLORS[t]}"></div>
<span><strong>${t}</strong> — ${typeCounts[t] || 0}</span>
</div>`;
});
// ── Subject × Type bars ─────────────────────────────
const stBars = document.getElementById('subjectTypeBars');
SUBJECTS.forEach(s => {
const sq = QUESTIONS.filter(q => q.subject === s);
stBars.innerHTML += `<p style="font-size:.8rem;font-weight:700;color:var(--text);margin-bottom:.5rem;margin-top:.75rem">${s}</p>`;
TYPES.forEach(t => {
const cnt = sq.filter(q => q.type === t).length;
const pct = sq.length ? (cnt/sq.length*100).toFixed(0) : 0;
stBars.innerHTML += `
<div class="bar-row">
<div class="bar-label">${t}</div>
<div class="bar-track"><div class="bar-fill" style="width:${pct}%;background:${TYPE_COLORS[t]}"></div></div>
<div class="bar-count">${cnt}</div>
</div>`;
});
});
// ── Year tiles ──────────────────────────────────────
const yearGrid = document.getElementById('yearGrid');
const allYears = [...new Set(QUESTIONS.map(q=>q.year))].sort().reverse();
const maxYearCnt = Math.max(...allYears.map(y => QUESTIONS.filter(q=>q.year===y).length));
allYears.forEach(y => {
const cnt = QUESTIONS.filter(q=>q.year===y).length;
const intensity = cnt / maxYearCnt;
const bg = `rgba(37,99,235,${0.1 + intensity * 0.7})`;
yearGrid.innerHTML += `
<div class="year-tile" style="background:${bg}">
<div class="year-cnt">${cnt}</div>
<div class="year-num">${y}</div>
</div>`;
});
// ── Topic table ─────────────────────────────────────
const topicMap = {};
QUESTIONS.forEach(q => {
const key = (q.topic || 'General') + '||' + q.subject;
topicMap[key] = (topicMap[key] || 0) + 1;
});
const topicRows = Object.entries(topicMap)
.map(([k, v]) => { const [t,s] = k.split('||'); return {topic:t, subject:s, count:v}; })
.sort((a,b) => b.count - a.count);
const maxTopicCnt = Math.max(...topicRows.map(r=>r.count));
const tbody = document.getElementById('topicBody');
topicRows.forEach(r => {
const barW = Math.round((r.count / maxTopicCnt) * 100);
tbody.innerHTML += `
<tr>
<td><strong>${r.topic}</strong></td>
<td><span class="badge badge-${r.subject.toLowerCase()}">${r.subject}</span></td>
<td>${r.count}</td>
<td>
<div class="progress-wrap" style="height:10px">
<div class="progress-bar" style="width:${barW}%"></div>
</div>
</td>
</tr>`;
});
})();
</script>
</body>
</html>
cd /home/daytona/workspace && ls -la kuhs-qbank/ && zip -r kuhs-qbank.zip kuhs-q…
cd /home/daytona/workspace && python3 -c " import zipfile, os files = ['kuhs-qba…
cd /home/daytona/workspace && tar -czf kuhs-qbank.tar.gz kuhs-qbank/ && echo "TA…
kuhs qbank.tar
Archive · GZ
index
HTML File · HTML
flashcard
HTML File · HTML
browse
HTML File · HTML
analysis
HTML File · HTML
styles
CSS File · CSS
data
JavaScript File · JS
| Page | What it does |
|---|---|
index.html | Home/setup — choose subjects (Anatomy / Physiology / Biochemistry), question types (LAQ/SAQ/MCQ), exam years, then launch Flashcard or Browse mode |
flashcard.html | 3D flip flashcard mode with confidence buttons (Easy / Hard / Again), MCQ option selection, keyboard shortcuts (Space=flip, ←→=navigate, 1/2/3=rate), session score summary |
browse.html | Full question list with live search, subject/type/year filters, expandable answers per question, "Expand All" per subject group |
analysis.html | Stats dashboard — total count cards, subject donut chart, type donut chart, Subject × Type bar breakdown, year heatmap tiles, topic frequency table |
data.js)QUESTIONS array. Each question object looks like:{
id: 1,
subject: "Anatomy", // Anatomy | Physiology | Biochemistry
topic: "Upper Limb",
type: "LAQ", // LAQ | SAQ | MCQ
year: "2023",
question: "Describe...",
answer: "The brachial plexus...",
// MCQ only:
options: ["A","B","C","D"],
correct: 1 // 0-based index
}
What to do next now
data.js.id | subject | topic | type | year | question | answer | option_a | option_b | option_c | option_d | correct
QUESTIONS array in data.js. No manual copy-paste needed.QUESTIONS array in data.js with your real questions. Everything else (all 4 pages) updates automatically — no other file changes needed.kuhs-qbank.vercel.app instantly| Feature | What it does |
|---|---|
| Excel → data.js converter | Upload your file, get ready-to-use data.js automatically |
| Mock paper generator | Randomly pick X LAQs + Y SAQs + Z MCQs per subject, printable PDF |
| Progress tracker | Saves your Easy/Hard/Again scores to localStorage between sessions |
| Topic filter on home page | Filter by sub-topic (e.g. "Upper Limb" only) |
| Dark mode | One toggle, full dark theme |
| PWA (offline app) | Works without internet, installable on phone |
data.js immediately. That's the only thing standing between you and a live working site.cd /home/daytona/workspace && python3 -c " import openpyxl wb = openpyxl.load_wo…
python3 -c " import openpyxl wb = openpyxl.load_workbook('/home/daytona/workspac…
python3 -c " import openpyxl, re wb = openpyxl.load_workbook('/home/daytona/work…
python3 -c " import openpyxl, re, json wb = openpyxl.load_workbook('/home/dayto…
python3 << 'PYEOF' import openpyxl, re, json wb = openpyxl.load_workbook('/home…
~/kuhs-qbank/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KUHS Physiology Question Bank</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css" />
<style>
.setup-grid { display:grid; grid-template-columns:1fr 1fr; gap:1.5rem; margin-top:2rem; }
@media(max-width:700px){ .setup-grid { grid-template-columns:1fr; } }
.setup-card { background:var(--surface); border:1px solid var(--border); border-radius:var(--radius); padding:1.5rem; }
.setup-card h3 { margin-bottom:1rem; display:flex; align-items:center; gap:.5rem; }
.cta-row { display:flex; gap:1rem; margin-top:1.5rem; flex-wrap:wrap; }
.count-banner {
background:var(--primary-l); border:1px solid #bfdbfe;
border-radius:var(--radius-sm); padding:.65rem 1rem;
font-size:.875rem; font-weight:600; color:var(--primary);
display:flex; align-items:center; gap:.5rem; margin-top:1.25rem;
}
.paper-cards { display:grid; grid-template-columns:1fr 1fr; gap:.875rem; margin-bottom:1rem; }
.paper-card {
border-radius:var(--radius); padding:1.1rem 1rem; cursor:pointer;
border:2px solid transparent; transition:var(--transition); text-align:center;
}
.paper-card .icon { font-size:1.75rem; margin-bottom:.35rem; }
.paper-card h4 { font-size:.875rem; font-weight:700; }
.paper-card p { font-size:.72rem; margin-top:.2rem; color:var(--text-muted); }
.paper-card.p1 { background:#dbeafe; }
.paper-card.p2 { background:#ede9fe; }
.paper-card.selected { border-color:var(--primary); box-shadow:var(--shadow); transform:translateY(-2px); }
/* Type pills — all 7 types */
.type-tag { padding:.35rem .875rem; border-radius:999px; border:2px solid var(--border); font-size:.78rem; font-weight:600; cursor:pointer; transition:var(--transition); background:var(--surface); color:var(--text-muted); user-select:none; }
.type-tag:hover { border-color:var(--primary); color:var(--primary); }
.type-tag.selected { background:var(--primary); border-color:var(--primary); color:#fff; }
/* Year range */
.year-scroll { display:flex; flex-wrap:wrap; gap:.4rem; max-height:110px; overflow-y:auto; padding-right:4px; }
.year-scroll::-webkit-scrollbar { width:4px; }
.year-scroll::-webkit-scrollbar-thumb { background:var(--border); border-radius:4px; }
</style>
</head>
<body>
<nav class="navbar">
<a class="navbar-brand" href="index.html">
<div class="logo-icon">K</div>
KUHS QBank <span class="sub">Physiology · 1st Year MBBS</span>
</a>
<div class="navbar-links">
<a href="index.html" class="active">Home</a>
<a href="browse.html">Browse</a>
<a href="flashcard.html">Flashcards</a>
<a href="analysis.html">Analysis</a>
</div>
</nav>
<!-- Hero -->
<div class="hero">
<div class="container">
<h1>KUHS Physiology<br>Previous Year Questions</h1>
<p>Kerala University of Health Sciences · 1st Year MBBS · 2015–2026</p>
<div class="badge-wrap" id="heroBadges">
<span class="hero-badge" id="hb-total">— Questions</span>
<span class="hero-badge">📄 Paper I & II</span>
<span class="hero-badge">🗓️ 2015–2026</span>
</div>
</div>
</div>
<main class="page-wrap">
<div class="container">
<div class="setup-grid">
<!-- Left: Paper + Type -->
<div class="setup-card">
<h3>📄 Paper Selection</h3>
<div class="paper-cards" id="paperCards">
<div class="paper-card p1 selected" data-paper="Physiology Paper I">
<div class="icon">📘</div>
<h4>Paper I</h4>
<p id="cnt-p1">— Qs</p>
</div>
<div class="paper-card p2 selected" data-paper="Physiology Paper II">
<div class="icon">📗</div>
<h4>Paper II</h4>
<p id="cnt-p2">— Qs</p>
</div>
</div>
<h3 style="margin-top:1rem">📝 Question Type</h3>
<div class="tag-group" id="typeTags" style="gap:.4rem"></div>
</div>
<!-- Right: Year + Mode -->
<div class="setup-card">
<h3>🗓️ Exam Years</h3>
<div style="display:flex;gap:.5rem;margin-bottom:.75rem">
<button class="btn btn-ghost btn-sm" id="selAllYears">Select All</button>
<button class="btn btn-ghost btn-sm" id="clearYears">Clear</button>
</div>
<div class="year-scroll" id="yearTags"></div>
<h3 style="margin-top:1.25rem">🎯 Study Mode</h3>
<div class="mode-toggle">
<button class="mode-btn active" id="modeFlash">🃏 Flashcards</button>
<button class="mode-btn" id="modeBrowse">📋 Browse All</button>
</div>
<div class="count-banner">
<span>✅</span>
<span id="selCount">0</span> questions selected
</div>
<div class="cta-row">
<button class="btn btn-primary btn-lg" id="startBtn">Start Studying →</button>
<a href="browse.html" class="btn btn-ghost btn-lg">Browse First</a>
</div>
</div>
</div>
</div>
</main>
<footer class="footer">
KUHS Physiology Question Bank · 1st Year MBBS · Built with ❤️ for medical students
</footer>
<script src="data.js"></script>
<script>
(function(){
// Counts
const p1 = QUESTIONS.filter(q=>q.paper==='Physiology Paper I').length;
const p2 = QUESTIONS.filter(q=>q.paper==='Physiology Paper II').length;
document.getElementById('cnt-p1').textContent = p1 + ' Qs';
document.getElementById('cnt-p2').textContent = p2 + ' Qs';
document.getElementById('hb-total').textContent = QUESTIONS.length + ' Questions';
const selPapers = new Set(PAPERS);
const selTypes = new Set(TYPES);
const selYears = new Set(YEARS);
let studyMode = 'flashcard';
// Type tags
const typeTagsEl = document.getElementById('typeTags');
TYPES.forEach(t => {
const span = document.createElement('span');
span.className = 'type-tag selected';
span.dataset.type = t;
span.textContent = TYPE_LABELS[t] || t;
typeTagsEl.appendChild(span);
});
// Year tags
const yearTagsEl = document.getElementById('yearTags');
YEARS.forEach(y => {
const span = document.createElement('span');
span.className = 'tag selected'; span.dataset.year = y; span.textContent = y;
yearTagsEl.appendChild(span);
});
function updateCount() {
const n = filterQuestions({ papers:[...selPapers], types:[...selTypes], years:[...selYears] }).length;
document.getElementById('selCount').textContent = n;
}
updateCount();
// Paper cards
document.getElementById('paperCards').addEventListener('click', e => {
const card = e.target.closest('[data-paper]');
if (!card) return;
const p = card.dataset.paper;
if (selPapers.has(p)) { selPapers.delete(p); card.classList.remove('selected'); }
else { selPapers.add(p); card.classList.add('selected'); }
updateCount();
});
// Type tags
typeTagsEl.addEventListener('click', e => {
const tag = e.target.closest('[data-type]');
if (!tag) return;
const t = tag.dataset.type;
if (selTypes.has(t)) { selTypes.delete(t); tag.classList.remove('selected'); }
else { selTypes.add(t); tag.classList.add('selected'); }
updateCount();
});
// Year tags
yearTagsEl.addEventListener('click', e => {
const tag = e.target.closest('[data-year]');
if (!tag) return;
const y = tag.dataset.year;
if (selYears.has(y)) { selYears.delete(y); tag.classList.remove('selected'); }
else { selYears.add(y); tag.classList.add('selected'); }
updateCount();
});
document.getElementById('selAllYears').addEventListener('click', () => {
YEARS.forEach(y => selYears.add(y));
yearTagsEl.querySelectorAll('[data-year]').forEach(t => t.classList.add('selected'));
updateCount();
});
document.getElementById('clearYears').addEventListener('click', () => {
selYears.clear();
yearTagsEl.querySelectorAll('[data-year]').forEach(t => t.classList.remove('selected'));
updateCount();
});
// Mode
document.getElementById('modeFlash').addEventListener('click', () => {
studyMode = 'flashcard';
document.getElementById('modeFlash').classList.add('active');
document.getElementById('modeBrowse').classList.remove('active');
});
document.getElementById('modeBrowse').addEventListener('click', () => {
studyMode = 'browse';
document.getElementById('modeBrowse').classList.add('active');
document.getElementById('modeFlash').classList.remove('active');
});
// Start
document.getElementById('startBtn').addEventListener('click', () => {
const params = new URLSearchParams({
papers: [...selPapers].join(','),
types : [...selTypes].join(','),
years : [...selYears].join(',')
});
window.location.href = (studyMode === 'flashcard' ? 'flashcard.html' : 'browse.html') + '?' + params;
});
})();
</script>
</body>
</html>
cat > /home/daytona/workspace/kuhs-qbank/index.html << 'HTMLEOF' <!DOCTYPE html>…
cat > /home/daytona/workspace/kuhs-qbank/browse.html << 'HTMLEOF' <!DOCTYPE html…
cat > /home/daytona/workspace/kuhs-qbank/flashcard.html << 'HTMLEOF' <!DOCTYPE h…
cat > /home/daytona/workspace/kuhs-qbank/analysis.html << 'HTMLEOF' <!DOCTYPE ht…
cd /home/daytona/workspace && python3 -c " import zipfile, os files = [ ('k…
cd /home/daytona/workspace && python3 -c " import zipfile, os files = [ 'ku…
cd /home/daytona/workspace && tar -czf kuhs-physiology.tar.gz kuhs-qbank/ && ls …
kuhs physiology.tar
Archive · GZ
data
JavaScript File · JS
index
HTML File · HTML
browse
HTML File · HTML
flashcard
HTML File · HTML
analysis
HTML File · HTML
| Field | What it maps to |
|---|---|
| Question | question text |
| Type | type — Long Essay→LAQ, Short Essay→SEQ, Short Answer→SAQ, MCQ→MCQ, One Word Answer→OWA, Draw & Label→DNL, Physiological Basis→PB |
| Chapter | paper — Paper I / Paper II |
| Year/File Name | yearFull (full string) + year (just the number e.g. 2023) |
| Times Repeated | repeated — drives the 🔥 badge on hot questions |
Show me how to deploy this to Vercel (step-by-step)
kuhs-physiology.tar.gz and extract it.kuhs-qbank with 6 files insidekuhs-qbank (or anything you like)kuhs-qbank folder:
index.htmlbrowse.htmlflashcard.htmlanalysis.htmlstyles.cssdata.jskuhs-qbank and click "Import"https://kuhs-qbank.vercel.app
.vercel.app:Got this
index.html because the files were uploaded inside a subfolder (kuhs-qbank/) instead of at the root level of the repository.kuhsqbank repo)kuhs-qbank — click into itindex.html, data.js, etc.)kuhs-qbank/index.html to just index.html → Commitkuhs-qbank folderkuhsqbank project → Settingskuhs-qbankWhere is root directorty
kuhs-qbank
kuhs-qbank folder for index.html" instead of looking at the top level of the repo.There is no deployments but built and depoyments is there
kuhs-qbank and click SaveOverview page
↓
Production Deployment card
↓
[ Visit ▼ ] ← click the ▼ arrow
↓
Redeploy ← click this
kuhs-qbank (with a hyphen) but you may have typed kuhsqbank (no hyphen).kuhs-qbank
kuhs and qbankkuhs-qbank ← with hyphenkuhs_qbank ← with underscorekuhsqbank ← no separatorI have tried with hypen also it shows same
kuhs-qbank (click it to see files inside) — this is the problemindex.html, data.js, etc.) — files are already at rootkuhs-qbank folder → click index.htmlkuhs-qbank/index.html to just index.htmlbrowse.html, flashcard.html, analysis.html, styles.css, data.jskuhs-qbank folderI see this
kuhs-physiology.tar.gz in your repokuhs-physiology.tar.gz in Downloadsindex.htmlbrowse.htmlflashcard.htmlanalysis.htmlstyles.cssdata.jsindex.html at the root and your site goes live ✅Got this again
index
HTML File · HTML
browse
HTML File · HTML
flashcard
HTML File · HTML
analysis
HTML File · HTML
styles
CSS File · CSS
data
JavaScript File · JS
This is what I see at github