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 0000000..718d6fe Binary files /dev/null and b/frontend/src/app 2/favicon.ico differ 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. +

+
+
+ + Vercel logomark + Deploy Now + + + Documentation + +
+
+
+ ); +} 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. -

+
@@ -225,6 +315,127 @@ export default function Home() { ))}
+ + {/* 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"] +}