From d1f2f1acb6566a3d821ceaaaf7d8578dad83c1c9 Mon Sep 17 00:00:00 2001 From: Aman koli <2025.amana@isu.ac.in> Date: Mon, 20 Jul 2026 22:37:02 +0530 Subject: [PATCH] feat(ui): update landing page styling, components, footer and fix docs routing --- backend/requirements.txt | 3 + frontend/.gitignore 2 | 41 + frontend/eslint.config 2.mjs | 18 + frontend/next.config 2.ts | 7 + frontend/next.config.ts | 11 +- frontend/package 2.json | 24 + frontend/package.json | 1 + frontend/pnpm-lock.yaml | 60 + frontend/pnpm-workspace 2.yaml | 3 + frontend/public/docs.html | 1188 +++++++++++++++++ frontend/public/file 2.svg | 1 + frontend/public/globe 2.svg | 1 + frontend/public/vercel 2.svg | 1 + frontend/public/window 2.svg | 1 + frontend/src/app 2/favicon.ico | Bin 0 -> 25931 bytes frontend/src/app 2/globals.css | 49 + frontend/src/app 2/layout.tsx | 30 + frontend/src/app 2/page.module.css | 142 ++ frontend/src/app 2/page.tsx | 66 + frontend/src/app/page.module.css | 222 ++- frontend/src/app/page.tsx | 245 +++- frontend/src/components/Grainient.css | 1 + .../motion/expanding-arrow-button.tsx | 194 +++ .../src/components/motion/text-reveal.tsx | 105 ++ frontend/tsconfig 2.json | 34 + 25 files changed, 2426 insertions(+), 22 deletions(-) create mode 100644 frontend/.gitignore 2 create mode 100644 frontend/eslint.config 2.mjs create mode 100644 frontend/next.config 2.ts create mode 100644 frontend/package 2.json create mode 100644 frontend/pnpm-workspace 2.yaml create mode 100644 frontend/public/docs.html create mode 100644 frontend/public/file 2.svg create mode 100644 frontend/public/globe 2.svg create mode 100644 frontend/public/vercel 2.svg create mode 100644 frontend/public/window 2.svg create mode 100644 frontend/src/app 2/favicon.ico create mode 100644 frontend/src/app 2/globals.css create mode 100644 frontend/src/app 2/layout.tsx create mode 100644 frontend/src/app 2/page.module.css create mode 100644 frontend/src/app 2/page.tsx create mode 100644 frontend/src/components/motion/expanding-arrow-button.tsx create mode 100644 frontend/src/components/motion/text-reveal.tsx create mode 100644 frontend/tsconfig 2.json diff --git a/backend/requirements.txt b/backend/requirements.txt index cf6e5b9..952e0f1 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -7,3 +7,6 @@ websockets # Pytest for testing pytest pytest-asyncio +requests +PyMuPDF +python-docx diff --git a/frontend/.gitignore 2 b/frontend/.gitignore 2 new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/frontend/.gitignore 2 @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/eslint.config 2.mjs b/frontend/eslint.config 2.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/frontend/eslint.config 2.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/frontend/next.config 2.ts b/frontend/next.config 2.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/frontend/next.config 2.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 30a7faa..ed1b718 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,5 +1,14 @@ import type { NextConfig } from 'next'; -const nextConfig: NextConfig = {/* config options here */}; +const nextConfig: NextConfig = { + async rewrites() { + return [ + { + source: '/docs', + destination: '/docs.html', + }, + ]; + }, +}; export default nextConfig; diff --git a/frontend/package 2.json b/frontend/package 2.json new file mode 100644 index 0000000..e668d88 --- /dev/null +++ b/frontend/package 2.json @@ -0,0 +1,24 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "next": "16.2.10", + "react": "19.2.4", + "react-dom": "19.2.4" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.10", + "typescript": "^5" + } +} diff --git a/frontend/package.json b/frontend/package.json index 6036da0..e75b9e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "format": "prettier --write ." }, "dependencies": { + "motion": "^12.42.2", "next": "16.2.10", "ogl": "^1.0.11", "react": "19.2.4", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 95a1e6f..0faeec5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + motion: + specifier: ^12.42.2 + version: 12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next: specifier: 16.2.10 version: 16.2.10(@babel/core@7.29.7)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1061,6 +1064,20 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} + framer-motion@12.42.2: + resolution: {integrity: sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1372,6 +1389,26 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + motion-dom@12.42.2: + resolution: {integrity: sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==} + + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} + + motion@12.42.2: + resolution: {integrity: sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2937,6 +2974,15 @@ snapshots: dependencies: is-callable: 1.2.7 + framer-motion@12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + motion-dom: 12.42.2 + motion-utils: 12.39.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + function-bind@1.1.2: {} function.prototype.name@1.2.0: @@ -3253,6 +3299,20 @@ snapshots: minimist@1.2.8: {} + motion-dom@12.42.2: + dependencies: + motion-utils: 12.39.0 + + motion-utils@12.39.0: {} + + motion@12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + framer-motion: 12.42.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + tslib: 2.8.1 + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + ms@2.1.3: {} nanoid@3.3.16: {} diff --git a/frontend/pnpm-workspace 2.yaml b/frontend/pnpm-workspace 2.yaml new file mode 100644 index 0000000..581a9d5 --- /dev/null +++ b/frontend/pnpm-workspace 2.yaml @@ -0,0 +1,3 @@ +ignoredBuiltDependencies: + - sharp + - unrs-resolver diff --git a/frontend/public/docs.html b/frontend/public/docs.html new file mode 100644 index 0000000..6c9abcd --- /dev/null +++ b/frontend/public/docs.html @@ -0,0 +1,1188 @@ + + + + + + + TWELVE — Documentation + + + + + + +
+ + +
+ +
+
AI-Powered Viva Assessment
+

Fairness
Through AI

+

No fear. No favour. Only knowledge.

+

Version 1.0  —  May 2026  —  Stage: Pre-Build / Ideation + Complete

+
+ +
+
+ 10 min + Per Student +
+
+ 0% + Examiner Bias +
+
+ 100% + Transparency +
+
+ 3x + Faster Throughput +
+
+ +
+ + +
+ + +
+ +

The Vision

+

The oral examination — the viva — is the most human assessment in academia. It is also the most + broken. A student who studies for three years walks into a room for ten minutes. What happens in + those ten minutes depends not on what they know but on who is sitting across from them.

+

TWELVE ends that.

+

TWELVE is a kiosk-based AI examination system installed on college-owned computers. It is not a + replacement for education. It is a replacement for the single most biased moment in education — the + oral viva conducted by a human examiner who has opinions, moods, favourites, and blind spots.

+

We are not anti-teacher. We are anti-bias.

+ +

What TWELVE Does

+
    +
  • Identifies the student before the session begins
  • +
  • Retrieves their submission history
  • +
  • Conducts a structured oral examination through voice and text
  • +
  • Evaluates responses against the college's own rubric
  • +
  • Produces a final score with a complete, auditable transcript
  • +
+

Every student. Same quality of questioning. Same standard of evaluation. Score reflects only what + they know.

+ +

The Name

+

Twelve is a number associated with completeness. Twelve months in a year. Twelve hours on a clock + face. Twelve members on a jury. TWELVE the system is exactly that: a complete, full-cycle assessment + that misses nothing, favours no one, and produces a judgment that stands.

+
+ + +
+ +

The Core Problem

+

The oral examination system in Indian colleges is broken at three levels simultaneously. Each villain + hurts a different group. All three are enabled by the same root cause: there is no system.

+ +
+ Villain 1 — The Biased Professor +

What they do: Assign marks based on favouritism, personal relationships, + attendance records, perceived attitude, or simple dislike.

+

Who gets hurt: The sincere student — the one who studied, understood the + material, but was never given space to properly demonstrate knowledge.

+

Why devastating: Unlike written exams, a viva leaves no record. No transcript. + No evidence. The student has no way to prove what they said or challenge what they were marked. +

+
+ +
+ Villain 2 — The Dishonest Student +

What they do: Submit AI-generated or copy-pasted work without understanding. + Prepare scripted answers for likely questions. Walk out with full marks because the examiner + didn't drill deep enough.

+

Why solvable: A dishonest student cannot answer a question they didn't prepare + for. TWELVE asks questions they cannot prepare for — because they are generated from the + student's own submission in real time.

+
+ +
+ Villain 3 — The Unstandardized Institution +

What they do: Run oral exams with no consistent rubric, no audit trail, no way + to compare results across sections or years, and no appeals process.

+

Who gets hurt: Everyone. Parents who cannot understand marks. Students who + cannot appeal without evidence. Regulators with no visibility.

+
+ +
+

The root cause: No one built a system to replace human subjectivity in oral + examination with something better. TWELVE is that system.

+
+
+ + +
+ +

The Solution

+

TWELVE replaces the biased human examiner in the oral viva with a structured, AI-driven interview + system. The system:

+
    +
  • Authenticates the student before the session begins
  • +
  • Reads their actual submitted work before asking a single question
  • +
  • Generates personalised, case-based questions from that work
  • +
  • Drills down on every answer with cross-questions
  • +
  • Detects gaps between claimed understanding and actual understanding
  • +
  • Tests core subject knowledge based on curriculum
  • +
  • Evaluates every response against the college's own rubric
  • +
  • Produces a final score using a panel of AI models
  • +
  • Stores a complete transcript for appeal and audit
  • +
+

No favouritism. No mood. No personal history. No recognition of face or name. Only what the student + knows.

+
+ + +
+ +

Core Beliefs

+ +
+
+
01
+
+
Bias is not personal. It is structural.
+

Individual professors are not all bad people. The system gives them no constraints, no + rubric, no record, and no accountability. The bias is the inevitable output of a broken + structure. Fix the structure. Fix the bias.

+
+
+
+
02
+
+
A viva should test knowledge, not confidence.
+

The student who is nervous and knows everything should not be penalised for their nerves. + The student who is confident and knows nothing should not be rewarded for their + confidence. TWELVE separates the two completely.

+
+
+
+
03
+
+
Cross-questioning is the only real test.
+

Any student can memorise an answer. A student who actually understands a concept can + answer the same concept asked six different ways. TWELVE asks the same concept six + different ways. Memorisation fails. Understanding passes.

+
+
+
+
04
+
+
The appeal process is not a weakness.
+

The AI decides. The professor reviews on appeal. This is not a compromise. This is the + correct structure. The AI is the examiner. The professor is the appellate authority. The + transcript is the evidence.

+
+
+
+
05
+
+
Transparency is the product.
+

Every question TWELVE asked. Every answer the student gave. Every score with reasoning. + All of it stored. All of it accessible. Nothing hidden. Transparency is not a feature. + It is the foundation.

+
+
+
+
06
+
+
Start narrow. Prove the core. Then scale.
+

TWELVE starts with BTech CSE viva examinations. One subject domain where answers are + objective, verifiable, and defensible. Prove it works there first. Then expand.

+
+
+
+
+ + +
+ +

Session Flow

+

This is what happens when a student sits down for their TWELVE viva. Every step is deliberate. Every + step serves a purpose.

+ +
+
+
0:00
+
+
Kiosk Activation
+

The college computer boots into TWELVE kiosk mode. No other application is accessible — + no browser, no file explorer, no task manager. The student sees only the TWELVE + interface.

+
+
+
+
0:30
+
+
Student Identification
+

Camera activates. TWELVE prompts the student to state their Name and Roll Number. The + student types or speaks their credentials. TWELVE cross-verifies against the college's + student database. At POC stage, identification is by Name and Roll Number only. Camera + is active exclusively for eye-contact monitoring, not identity verification.

+
+
+
+
1:00
+
+
Submission Retrieval
+

Once identity is confirmed, TWELVE silently retrieves the student's submission data: + project submission, Problem Statement assigned, assignment history for the current + semester, and any prior assessment records.

+
+
+
+
1:30
+
+
Pre-Session Analysis
+

TWELVE reads the student's submission and builds a question tree silently while a welcome + screen is shown. The AI identifies key technical terms used, the PS and approach taken, + technologies mentioned, and any claims that can be probed.

+
+
+
+
3:00
+
+
PS Verification and Project Deep Dive
+

TWELVE opens with the student's own Problem Statement, then drills into the submission + using the student's own words against them. Questions cannot be prepared for because + they are built from the student's submission in real time.

+
+
+
+
6:00
+
+
Cross-Questioning
+

The same concept is asked again from a different angle, in a different scenario, with + different framing. A student who memorised an answer will give the same scripted + response to all variants. A student who understood will adapt. TWELVE detects the + difference.

+
+
+
+
7:30
+
+
Core Subject Knowledge
+

After project-specific questioning, TWELVE shifts to curriculum-based knowledge for the + student's current semester. Questions are randomised from the curated question bank.

+
+
+
+
8:30
+
+
Session Close and Feedback
+

TWELVE closes with brief spoken feedback: what areas were strong, what needs development, + one concrete suggestion for improvement. This feedback is developmental only — not part + of the score.

+
+
+
+
10:00
+
+
Score Generation and Logging
+

After the student leaves: final score computed by AI panel, full transcript saved, + session log stored for audit and appeal, next candidate called.

+
+
+
+
+ + +
+ +

System Architecture

+
[College Admin Interface] + | + | uploads student data, curriculum, rubric + v + [TWELVE Data Layer] + — Student records + — Submission files + — Curriculum question bank + — Marking rubric + | + v + [Kiosk Interface — College Computer] + — Full OS lockdown + — Camera feed (eye-contact monitoring) + — Audio input (student voice) + — Display (questions + AI avatar) + | + v + [Session Engine] + — Identity verification module + — Submission retrieval + — Question tree generator + — Real-time response evaluator + — Cross-question logic engine + — Session transcript logger + | + v + [AI Panel — Multi-Model Scoring] + — Model A: Factual correctness (40%) + — Model B: Application depth (40%) + — Model C: Coherence consistency (20%) + — Aggregator: Weighted average + | + v + [Output Layer] + — Final score + — Full session transcript + — Audit log + — Feedback report + — Appeal-ready package
+
+ + +
+ +

The AI Panel

+

TWELVE does not use a single AI model to evaluate answers. It uses a panel of multiple models — each + trained on a different dataset with a different reasoning approach — to evaluate every student + response independently.

+ +
+
+ Model A + Curriculum Accuracy + 40% +

Evaluates factual and technical correctness against academic standards. Trained on CSE + textbooks, university syllabi, and exam papers.

+
+
+ Model B + Application Depth + 40% +

Evaluates whether the student can apply the concept. Trained on industry problem-solving + patterns, technical interviews, and case studies.

+
+
+ Model C + Coherence Consistency + 20% +

Evaluates internal consistency across the session. Trained on cross-examination transcripts + and Q&A sessions.

+
+
+ +

The Aggregator

+

After all three models score independently, the Aggregator collects scores, applies the college's + pre-defined rubric weightage per section, computes a weighted rational average, and flags any answer + where panel disagreement exceeds a defined threshold for potential human review.

+ +
+

Key principle: Three models trained on different data are not an echo chamber. + They will produce genuinely different evaluations. The aggregator is not averaging sameness — it + is reconciling difference.

+
+
+ + +
+ +

Questioning Strategy

+ +

Layer 1 — Submission-Specific Questions

+

Generated from reading the student's own submitted work. No two students receive the same questions + because no two students submitted the same work. Questions never ask what the student already wrote + — they ask what follows from it.

+ +

Layer 2 — Drill-Down Questions

+

For every answer given, TWELVE generates a follow-up that goes one level deeper.

+
"You said you used a hash table for O(1) lookup. + What happens to your lookup time when hash collisions occur?" + + "You said the API returned JSON. + What would you do if the API returned malformed JSON?" + + "You said you tested this on 100 records. + What would break if it ran on 10 million records?"
+ +

Layer 3 — Case-Based Variant Questions

+

Same concept. Different scenario. Different framing. Asked to verify understanding is transferable. A + student who memorised "merge sort is O(n log n)" will give the same answer to all three variants. A + student who understands sorting will give three different answers.

+ +

Layer 4 — Core Subject Questions

+

From the curated question bank. Semester and year specific. Randomised per session. Cannot be + predicted or prepared for as a specific set.

+
+ + +
+ +

Marking and Evaluation Framework

+ +

Who Defines the Rubric

+

The college defines the marking rubric before the examination begins. Professors set the criteria + beforehand: what sections are assessed, what weightage each section carries, what constitutes a + passing answer, and what the total marks are. TWELVE applies the institution's standards with + perfect consistency — something a human examiner cannot guarantee across 60 students over two hours. +

+ +

What Gets Scored

+
    +
  • Project understanding — PS explanation, approach, and implementation reasoning
  • +
  • Cross-question performance — ability to handle variant questions
  • +
  • Core subject knowledge — curriculum-based questions
  • +
+ +

What Is Never Scored

+
    +
  • Eye contact — anti-cheat flag only, never a mark
  • +
  • Vocal confidence — feedback only, never marks
  • +
  • Speed of answering — time taken is logged but not scored
  • +
  • Appearance or presentation
  • +
+
+ + +
+ +

The Appeal Process

+ +

The AI decision is the default final mark. On appeal, initiated by the student within a defined + window, the professor reviews:

+
    +
  1. The full session transcript — every question TWELVE asked, word for word
  2. +
  3. Every answer the student gave, transcribed and timestamped
  4. +
  5. The AI's score for each answer with the reasoning provided
  6. +
  7. The marking rubric used for evaluation
  8. +
  9. Any eye-contact flags logged during the session
  10. +
+

The professor may override the AI's mark if they find a clear and articulable error. The override + must be documented — the professor must state what was wrong with the AI's scoring for the override + to be recorded.

+ +
+

This is not a compromise. This is the correct structure.

+

The AI is the examiner. The professor is the appellate authority. The transcript is the evidence. + This mirrors how functional judicial systems work — the trial court decides; appeals go to a + higher authority; the record is the evidence.

+
+ +

If a student disputes their mark, the professor can point to the exact transcript. There is no "the + professor didn't like me" argument available because the professor did not conduct the examination. + The override itself is also logged and auditable.

+
+ + +
+ +

Technology Stack

+ +

Frontend — Kiosk Interface

+
    +
  • Kiosk shell: Electron.js — cross-platform desktop kiosk, OS-level lockdown
  • +
  • UI framework: React with Tailwind CSS
  • +
  • Speech-to-text: OpenAI Whisper (local)
  • +
  • Text-to-speech: ElevenLabs API with pyttsx3 local fallback
  • +
  • Eye-contact analysis: MediaPipe Face Mesh via OpenCV
  • +
+ +

Backend — Session Engine

+
    +
  • API server: FastAPI (Python) — high performance, async
  • +
  • Session management: Redis
  • +
  • LLM integration: Anthropic Claude API (primary), OpenAI GPT-4o (fallback)
  • +
  • Fine-tuned models: Hugging Face Transformers
  • +
  • Document parsing: PyMuPDF, python-docx, pypdf
  • +
+ +

AI Panel Models

+
    +
  • Model A (Curriculum): Mistral 7B fine-tuned on academic CSE textbooks
  • +
  • Model B (Application): LLaMA 3 fine-tuned on technical interview datasets
  • +
  • Model C (Coherence): Mistral 7B fine-tuned on Q&A transcripts
  • +
  • Question Generator: Claude API (prompt-engineered)
  • +
+ +

Database

+
    +
  • Primary DB: PostgreSQL with JSONB support
  • +
  • Session cache: Redis
  • +
  • File storage: Local filesystem (POC) then AWS S3 at scale
  • +
+ +

Kiosk OS Lockdown

+
    +
  • OS: Windows 10/11 LTSC — most common in Indian college labs
  • +
  • Kiosk mode: Windows Assigned Access with Electron
  • +
  • USB blocking: Windows Group Policy
  • +
  • Network: Local network only during session
  • +
+
+ + +
+ +

Roadmap

+ +
+
Now — POC
2 weeks
+
+
Prove the Core
+

Submission parser. Question generator via Claude API. Student answer interface. Basic + scoring. Transcript output. Local prototype only.

+
+
+
+
Next — Pilot
4–8 weeks
+
+
First Real Batch
+

Kiosk mode. Full session flow. Identity verification. Multi-model panel. Eye-contact + monitoring. Admin portal. Run one real batch at own college in parallel with traditional + viva.

+
+
+
+
Month 3–4
+
+
First External Sale
+

Comparative pilot data ready. Approach two to three nearby engineering colleges. + Multi-college isolation. Formal admin portal. Begin question bank expansion.

+
+
+
+
Month 6+
+
+
Scale
+

LMS integration. Multi-department expansion. Self-service onboarding. Biometric verification + post legal clearance. 20+ college target.

+
+
+ +
+

The final test for every feature:

+

Does this make the viva fairer — or does it just make it faster? If it just makes it faster, + rebuild it until it makes it fairer. A faster unfair examination is not a product. It is a more + efficient injustice.

+
+ +

The One Thing That Cannot Be Compromised

+

The transcript is sacred. Every question TWELVE asked. Every answer the student gave. Every score + with its reasoning. Stored. Immutable. Accessible.

+

If TWELVE ever deletes a transcript, edits a transcript, or hides a transcript — it has become the + same biased examiner it was built to replace. The transcript is the difference between TWELVE and + everything that came before it.

+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/public/file 2.svg b/frontend/public/file 2.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/frontend/public/file 2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/globe 2.svg b/frontend/public/globe 2.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/frontend/public/globe 2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vercel 2.svg b/frontend/public/vercel 2.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/frontend/public/vercel 2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/window 2.svg b/frontend/public/window 2.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/frontend/public/window 2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/app 2/favicon.ico b/frontend/src/app 2/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/frontend/src/app 2/globals.css b/frontend/src/app 2/globals.css new file mode 100644 index 0000000..4c18fb3 --- /dev/null +++ b/frontend/src/app 2/globals.css @@ -0,0 +1,49 @@ +:root { + --background: #ffffff; + --foreground: #171717; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +html { + height: 100%; +} + +html, +body { + max-width: 100vw; + overflow-x: hidden; +} + +body { + min-height: 100%; + display: flex; + flex-direction: column; + color: var(--foreground); + background: var(--background); + font-family: Arial, Helvetica, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +a { + color: inherit; + text-decoration: none; +} + +@media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } +} diff --git a/frontend/src/app 2/layout.tsx b/frontend/src/app 2/layout.tsx new file mode 100644 index 0000000..1da6a2f --- /dev/null +++ b/frontend/src/app 2/layout.tsx @@ -0,0 +1,30 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Create Next App", + description: "Generated by create next app", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/frontend/src/app 2/page.module.css b/frontend/src/app 2/page.module.css new file mode 100644 index 0000000..643fe27 --- /dev/null +++ b/frontend/src/app 2/page.module.css @@ -0,0 +1,142 @@ +.page { + --background: #fafafa; + --foreground: #fff; + + --text-primary: #000; + --text-secondary: #666; + + --button-primary-hover: #383838; + --button-secondary-hover: #f2f2f2; + --button-secondary-border: #ebebeb; + + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + font-family: var(--font-geist-sans); + background-color: var(--background); +} + +.main { + display: flex; + flex: 1; + width: 100%; + max-width: 800px; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + background-color: var(--foreground); + padding: 120px 60px; +} + +.intro { + display: flex; + flex-direction: column; + align-items: flex-start; + text-align: left; + gap: 24px; +} + +.intro h1 { + max-width: 320px; + font-size: 40px; + font-weight: 600; + line-height: 48px; + letter-spacing: -2.4px; + text-wrap: balance; + color: var(--text-primary); +} + +.intro p { + max-width: 440px; + font-size: 18px; + line-height: 32px; + text-wrap: balance; + color: var(--text-secondary); +} + +.intro a { + font-weight: 500; + color: var(--text-primary); +} + +.ctas { + display: flex; + flex-direction: row; + width: 100%; + max-width: 440px; + gap: 16px; + font-size: 14px; +} + +.ctas a { + display: flex; + justify-content: center; + align-items: center; + height: 40px; + padding: 0 16px; + border-radius: 128px; + border: 1px solid transparent; + transition: 0.2s; + cursor: pointer; + width: fit-content; + font-weight: 500; +} + +a.primary { + background: var(--text-primary); + color: var(--background); + gap: 8px; +} + +a.secondary { + border-color: var(--button-secondary-border); +} + +/* Enable hover only on non-touch devices */ +@media (hover: hover) and (pointer: fine) { + a.primary:hover { + background: var(--button-primary-hover); + border-color: transparent; + } + + a.secondary:hover { + background: var(--button-secondary-hover); + border-color: transparent; + } +} + +@media (max-width: 600px) { + .main { + padding: 48px 24px; + } + + .intro { + gap: 16px; + } + + .intro h1 { + font-size: 32px; + line-height: 40px; + letter-spacing: -1.92px; + } +} + +@media (prefers-color-scheme: dark) { + .logo { + filter: invert(); + } + + .page { + --background: #000; + --foreground: #000; + + --text-primary: #ededed; + --text-secondary: #999; + + --button-primary-hover: #ccc; + --button-secondary-hover: #1a1a1a; + --button-secondary-border: #1a1a1a; + } +} diff --git a/frontend/src/app 2/page.tsx b/frontend/src/app 2/page.tsx new file mode 100644 index 0000000..7b947a2 --- /dev/null +++ b/frontend/src/app 2/page.tsx @@ -0,0 +1,66 @@ +import Image from "next/image"; +import styles from "./page.module.css"; + +export default function Home() { + return ( +
+
+ Next.js logo +
+

To get started, edit the page.tsx file.

+

+ Looking for a starting point or more instructions? Head over to{" "} + + Templates + {" "} + or the{" "} + + Learning + {" "} + center. +

+
+ +
+
+ ); +} diff --git a/frontend/src/app/page.module.css b/frontend/src/app/page.module.css index 71df1a2..00ca9b0 100644 --- a/frontend/src/app/page.module.css +++ b/frontend/src/app/page.module.css @@ -228,9 +228,8 @@ display: flex; justify-content: space-between; align-items: center; - margin-bottom: 100px; - max-width: 1000px; - margin: 0 auto; + max-width: 1400px; + margin: 0 auto 100px auto; width: 100%; padding: 0 20px; } @@ -268,10 +267,11 @@ .cardsContainer { display: flex; + margin-left: 100px; gap: 24px; overflow-x: auto; /* Use padding to align exactly with the max-width container above it */ - padding: 0 20px 40px max(20px, calc((100vw - 1000px) / 2 + 20px)); + padding: 20px 20px 40px max(20px, calc((100vw - 1000px) / 2 + 20px)); scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none; @@ -439,6 +439,186 @@ font-size: 28px; } +/* New Large Cards Section */ +.largeCardsSection { + padding: 100px 20px; + max-width: 1440px; + margin: 0 auto; +} + +.largeCardsGrid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 32px; +} + +.largeCard { + position: relative; + border-radius: 32px; + overflow: hidden; + min-height: 580px; + display: flex; + background-color: #1a1a1a; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.largeCardBg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + opacity: 0.9; +} + +.largeCardContent { + position: relative; + z-index: 1; + display: flex; + width: 100%; + padding: 48px; + justify-content: space-between; +} + +.largeCardLeft { + display: flex; + flex-direction: column; + justify-content: space-between; + max-width: 50%; +} + +.largeCardTitle { + font-family: "Cormorant Garamond", serif; + font-size: 3rem; + font-weight: 500; + color: white; + margin-bottom: 16px; + line-height: 1.1; + letter-spacing: -0.01em; +} + +.largeCardBtn { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 100px; + padding: 12px 24px; + color: white; + font-size: 0.9rem; + cursor: pointer; + backdrop-filter: blur(10px); + display: inline-flex; + align-items: center; + gap: 8px; + transition: all 0.2s; + width: fit-content; +} + +.largeCardBtn:hover { + background: rgba(255, 255, 255, 0.2); +} + +.largeCardRight { + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-end; +} + +.mockUiPanel { + background: rgba(10, 10, 10, 0.6); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 24px; + padding: 24px; + width: 280px; + backdrop-filter: blur(20px); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4); + display: flex; + flex-direction: column; + gap: 16px; +} + +.footer { + margin: 120px auto 40px auto; + width: calc(100% - 40px); + max-width: 1400px; + padding: 80px 60px 40px 60px; + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 32px; + background: linear-gradient(to bottom, #161616, #0a0a0a); +} + +.footerContent { + display: flex; + justify-content: space-between; + gap: 60px; + max-width: 1200px; + margin: 0 auto; +} + +.footerLeft { + max-width: 300px; +} + +.footerLogo { + font-family: "Cormorant Garamond", serif; + font-size: 1.8rem; + letter-spacing: 0.1em; + text-transform: uppercase; + color: white; + margin-bottom: 20px; + display: inline-block; +} + +.footerDescription { + color: rgba(255, 255, 255, 0.5); + font-size: 0.95rem; + line-height: 1.5; +} + +.footerRight { + display: flex; + gap: 80px; +} + +.footerLinksGroup { + display: flex; + flex-direction: column; + gap: 16px; +} + +.footerLinksGroup h4 { + color: white; + font-size: 0.95rem; + font-weight: 500; + margin-bottom: 8px; +} + +.footerLinksGroup a { + color: rgba(255, 255, 255, 0.5); + text-decoration: none; + font-size: 0.9rem; + transition: color 0.2s; +} + +.footerLinksGroup a:hover { + color: white; +} + +.footerBottom { + margin-top: 80px; + padding-top: 24px; + border-top: 1px solid rgba(255, 255, 255, 0.05); + display: flex; + justify-content: space-between; + color: rgba(255, 255, 255, 0.3); + font-size: 0.85rem; + max-width: 1200px; + margin-left: auto; + margin-right: auto; +} + @media (max-width: 768px) { .navLinks { display: none; @@ -458,4 +638,38 @@ .cardsContainer { padding: 0 20px 20px 20px; } + + .largeCardsGrid { + grid-template-columns: 1fr; + } + + .largeCardContent { + flex-direction: column; + padding: 32px; + gap: 40px; + } + + .largeCardLeft { + max-width: 100%; + } + + .largeCardRight { + align-items: flex-start; + } + + .footerContent { + flex-direction: column; + gap: 40px; + } + + .footerRight { + flex-direction: column; + gap: 40px; + } + + .footerBottom { + flex-direction: column; + gap: 12px; + align-items: center; + } } \ No newline at end of file diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index a8a572e..b5aca5e 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,11 +1,62 @@ "use client"; -import { useRef } from "react"; +import { useRef, useState, useEffect } from "react"; import styles from "./page.module.css"; import Grainient from "../components/Grainient"; +import { TextReveal } from "../components/motion/text-reveal"; +import { ExpandingArrowButton } from "../components/motion/expanding-arrow-button"; export default function Home() { const scrollContainerRef = useRef(null); + const [qsCount, setQsCount] = useState(12); + const [confidence, setConfidence] = useState(94.2); + const [typewriterText, setTypewriterText] = useState(""); + + useEffect(() => { + const words = ["Analyzing AST...", "Finding Vulnerabilities...", "Generating Qs...", "Active ON"]; + let wordIdx = 0; + let charIdx = 0; + let isDeleting = false; + let typingTimeout: NodeJS.Timeout; + + const type = () => { + const currentWord = words[wordIdx]; + if (isDeleting) { + charIdx--; + } else { + charIdx++; + } + + setTypewriterText(currentWord.substring(0, charIdx) + "|"); + + let delay = 60; + if (!isDeleting && charIdx === currentWord.length) { + delay = 1500; + isDeleting = true; + } else if (isDeleting && charIdx === 0) { + isDeleting = false; + wordIdx = (wordIdx + 1) % words.length; + delay = 300; + } + + typingTimeout = setTimeout(type, delay); + }; + + typingTimeout = setTimeout(type, 100); + + const numInterval = setInterval(() => { + setQsCount(prev => prev < 45 ? prev + 1 : 12); + setConfidence(prev => { + const next = prev + (Math.random() * 0.8 - 0.2); + return next > 99.9 ? 94.2 : next; + }); + }, 400); + + return () => { + clearTimeout(typingTimeout); + clearInterval(numInterval); + }; + }, []); const scrollLeft = () => { if (scrollContainerRef.current) { @@ -76,11 +127,21 @@ export default function Home() { ), content: ( -
- - - - Generated +
+
+ Viva Engine +
+ {qsCount} Qs + Ready +
+
+
+ Adversarial Mode +
+ {typewriterText} + +
+
) }, @@ -93,10 +154,21 @@ export default function Home() { ), content: ( -
-
A
-
B
-
C
+
+
+ Active Agents +
+ 3 Models + Sync +
+
+
+ Confidence Score +
+ {confidence.toFixed(1)}% + High +
+
) }, @@ -109,9 +181,21 @@ export default function Home() { ), content: ( -
- 🔒 - Sealed +
+
+ Session State +
+ Sealed + Secured +
+
+
+ Audit Log +
+ 0x8F2... + Tx ID +
+
) } @@ -157,7 +241,7 @@ export default function Home() {
Vision Github - Docs + Docs
@@ -168,9 +252,15 @@ export default function Home() { SYSTEM AI-Powered Viva Assessment
-

- Fairness Through AI.
No fear. No favour. Only knowledge. -

+ + + {/* Two Large Cards Section */} +
+
+ + {/* Card 1: The Viva Engine */} +
+
+ +
+
+
+
+

Submission
Intelligence

+

+ The AI reconstructs your project into a semantic graph of concepts, dependencies, and implementation paths—creating a viva unique to your work. +

+
+
+ View Demo +
+
+
+
+
+ Knowledge Graph +
+ {130 + qsCount} Concepts + Generated +
+
+
+ Question Tree +
+ Ready + Dynamic +
+
+
+
+
+
+ + {/* Card 2: Immutable Transcript */} +
+
+ +
+
+
+
+

Consensus
Evaluation

+

+ Every answer is reviewed by multiple reasoning models before a unified score is produced. No assumptions. No examiner bias. Only evidence. +

+
+
+ Explore Tx +
+
+
+
+
+ AI Panel +
+ 3 Models + Online +
+
+
+ Confidence +
+ {confidence.toFixed(1)}% + Stable +
+
+
+
+
+
+
+
+ + {/* Footer */} +
); } diff --git a/frontend/src/components/Grainient.css b/frontend/src/components/Grainient.css index c6976d4..4b7439b 100644 --- a/frontend/src/components/Grainient.css +++ b/frontend/src/components/Grainient.css @@ -6,4 +6,5 @@ height: 100%; overflow: hidden; border-radius: inherit; /* To inherit the 40px border radius of the landing card */ + pointer-events: none; } diff --git a/frontend/src/components/motion/expanding-arrow-button.tsx b/frontend/src/components/motion/expanding-arrow-button.tsx new file mode 100644 index 0000000..99c0692 --- /dev/null +++ b/frontend/src/components/motion/expanding-arrow-button.tsx @@ -0,0 +1,194 @@ +"use client"; + +import { motion, useReducedMotion, type HTMLMotionProps } from "motion/react"; +import { forwardRef, useState, type FocusEvent, type MouseEvent, type ReactNode } from "react"; + +const EASE_OUT = [0.25, 0.1, 0.25, 1]; +const SPRING_LAYOUT = { type: "spring", stiffness: 300, damping: 30 }; +const SPRING_PRESS = { type: "spring", stiffness: 400, damping: 30 }; + +export interface ExpandingArrowButtonProps extends Omit, "children"> { + children: ReactNode; + accentClassName?: string; + labelClassName?: string; +} + +const ARROW_OPACITY = [1, 0.78, 0.54, 0.32, 0.16] as const; + +function DottedChevron({ className, style }: { className?: string; style?: React.CSSProperties }) { + return ( + + ); +} + +export const ExpandingArrowButton = forwardRef( + function ExpandingArrowButton( + { + children, + className, + accentClassName, + labelClassName, + disabled, + onMouseEnter, + onMouseLeave, + onFocus, + onBlur, + style, + ...rest + }, + ref, + ) { + const reduce = useReducedMotion(); + const [hovered, setHovered] = useState(false); + const [focused, setFocused] = useState(false); + + // Active state drives the arrow expansion. + const active = !disabled && (hovered || focused); + const layoutTransition = reduce ? { duration: 0 } : SPRING_LAYOUT; + + const handleMouseEnter = (event: MouseEvent) => { + setHovered(true); + onMouseEnter?.(event); + }; + + const handleMouseLeave = (event: MouseEvent) => { + setHovered(false); + onMouseLeave?.(event); + }; + + const handleFocus = (event: FocusEvent) => { + setFocused(true); + onFocus?.(event); + }; + + const handleBlur = (event: FocusEvent) => { + setFocused(false); + onBlur?.(event); + }; + + return ( + + + + + {children} + + + ); + }, +); diff --git a/frontend/src/components/motion/text-reveal.tsx b/frontend/src/components/motion/text-reveal.tsx new file mode 100644 index 0000000..e6a1367 --- /dev/null +++ b/frontend/src/components/motion/text-reveal.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { motion, type Transition, useInView, useReducedMotion } from "motion/react"; +import { useRef, type ElementType, type ReactNode } from "react"; + +const EASE_OUT = [0.25, 0.1, 0.25, 1]; + +type SplitMode = "word" | "char"; + +export interface TextRevealProps { + text: string | string[]; + as?: ElementType; + className?: string; + split?: SplitMode; + stagger?: number; + delay?: number; + blur?: number; + yOffset?: string | number; + spring?: { stiffness?: number; damping?: number; mass?: number }; + once?: boolean; + whileInView?: boolean; + children?: ReactNode; +} + +const DEFAULT_SPRING = { stiffness: 140, damping: 26, mass: 1.2 }; + +export function TextReveal({ + text, + as: Comp = "span", + className, + split = "word", + stagger = 0.09, + delay = 0, + blur = 12, + yOffset = "40%", + spring, + once = true, + whileInView = false, + children, +}: TextRevealProps) { + const ref = useRef(null); + const inView = useInView(ref, { once, amount: 0.4 }); + const reduce = useReducedMotion(); + const shouldAnimate = whileInView ? inView : true; + + const lines = Array.isArray(text) ? text : [text]; + const s = { ...DEFAULT_SPRING, ...spring }; + + let unitIndex = 0; + const lineCounts = new Map(); + + return ( + + {lines.map((line) => { + const units = split === "word" ? line.split(" ") : Array.from(line); + const lineCount = lineCounts.get(line) ?? 0; + lineCounts.set(line, lineCount + 1); + const lineKey = `${line}-${lineCount}`; + const unitCounts = new Map(); + + return ( + + {units.map((unit, i) => { + const d = delay + unitIndex * stagger; + unitIndex += 1; + const unitCount = unitCounts.get(unit) ?? 0; + unitCounts.set(unit, unitCount + 1); + const unitKey = `${unit}-${unitCount}`; + const initial = reduce + ? { opacity: 0 } + : { y: yOffset, opacity: 0, filter: `blur(${blur}px)` }; + const animate = shouldAnimate + ? reduce + ? { opacity: 1 } + : { y: 0, opacity: 1, filter: "blur(0px)" } + : initial; + const transition: Transition = reduce + ? { opacity: { duration: 0.25, ease: EASE_OUT, delay: d * 0.3 } } + : { + y: { type: "spring" as const, ...s, delay: d }, + opacity: { duration: 0.7, ease: EASE_OUT, delay: d }, + filter: { duration: 0.9, ease: EASE_OUT, delay: d }, + }; + return ( + + {unit} + {split === "word" && i < units.length - 1 ? ( +   + ) : null} + + ); + })} + + ); + })} + {children} + + ); +} diff --git a/frontend/tsconfig 2.json b/frontend/tsconfig 2.json new file mode 100644 index 0000000..cf9c65d --- /dev/null +++ b/frontend/tsconfig 2.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +}