Skip to content

smart-developer1791/preact-threejs-3d-testing-lab

Repository files navigation

✦ 3D Testing Lab

Immersive full-screen multi-step testing wizard with particle tunnel backgrounds

Preact Three.js GSAP Anime.js Tailwind CSS Vite Netlify License: MIT


A data-driven, API-ready 3D testing experience with timed multi-step questions, pass/fail scoring, detailed answer review, confetti completion effects, and smooth GSAP + anime.js transitions. Fully responsive mobile-first layout with native scroll.


✨ Features

Feature
🧪 Test-Focused Flow — Built for quizzes/exams with score calculation and pass/fail logic
⏱️ Countdown Timer — Optional per-test time limit with automatic submit on timeout
📊 Live Progress — Animated progress bar + step indicators across all questions
🧠 Data-Driven — One JSON-like config controls the entire test
🧩 Multiple Question Typesselect, multiselect, yesno, number, text, textarea, rating, slider, emoji, email, date
Validation — Required-field validation before moving to the next step
🧾 Answer Review — Result screen shows user answers vs correct answers with explanations
🚀 3D Visuals — Particle tunnel background with mood transitions and celebration mode
🎉 Completion Effects — Canvas-confetti burst + animated result entry
📱 Responsive UX — Mobile-first design with native scroll and touch-safe controls

🛠 Tech Stack

Layer Technology
Framework Preact 10
3D Engine Three.js
Transitions GSAP 3
Micro-motion anime.js
Celebration canvas-confetti
Styling Tailwind CSS 3
Build Vite 5

🚀 Quick Start

Prerequisites

  • Node.js 18+ and npm 9+

Install & Run

npm install
npm run dev

Open http://localhost:5173

Production Build

npm run build
npm run preview

🌐 Deployment

Netlify (Recommended)

  1. Push repository to GitHub
  2. Connect repo in Netlify
  3. Build settings are preconfigured in netlify.toml
  4. Deploy

Deploy to Netlify


📋 Test Configuration

Everything lives in src/data/testData.js.

Top-Level Structure

{
  id: "frontend-essentials-test",
  title: "Frontend Essentials Test",
  subtitle: "8 quick questions...",
  version: "1.0.0",

  branding: {
    logoText: "✦ TestLab 3D",
    completionEmoji: "🏁"
  },

  settings: {
    showProgressBar: true,
    showStepIndicators: true,
    showStepCount: true,
    submitButtonText: "Finish Test",
    nextButtonText: "Next",
    prevButtonText: "Back",
    passThreshold: 70,
    timeLimitSec: 360
  },

  steps: [ /* ... */ ]
}

Step Structure

{
  id: "q1",
  title: "JavaScript Fundamentals",
  subtitle: "Choose one correct answer",
  icon: "🧠",
  fields: [ /* ... */ ]
}

Field Structure

{
  id: "js_scope",
  type: "select",
  label: "Which keyword creates a block-scoped variable?",
  required: true,
  options: [{ value: "let", label: "let" }],

  // scoring fields
  correctAnswer: "let",
  explanation: "Shown in result review"
}

🧮 Scoring Logic

  • Only fields that have correctAnswer are included in scoring.
  • multiselect answers are compared as unordered sets.
  • String answers are compared case-insensitively (trimmed).
  • Other types are compared by strict equality.
  • Pass/fail is determined by settings.passThreshold (default fallback: 70).

🔌 API Integration

Loading Questions From API

Return the same schema as testConfig.

// App.jsx sketch
const TEST_CONFIG_URL = import.meta.env.VITE_TEST_CONFIG_URL;
const [config, setConfig] = useState(testConfig); // local fallback

useEffect(() => {
  if (!TEST_CONFIG_URL) return;

  fetch(TEST_CONFIG_URL)
    .then((res) => {
      if (!res.ok) throw new Error(`Config load failed: ${res.status}`);
      return res.json();
    })
    .then((remoteConfig) => setConfig(remoteConfig))
    .catch((err) => {
      console.error("Config load error:", err);
      // fallback stays as local testConfig
    });
}, []);

Submitting Results To API

TestWizard already emits this object to onComplete(finalResult):

{
  answers: { [fieldId]: value },
  timedOut: boolean,
  elapsedSec: number,
  secondsLeft: number | null
}
// App.jsx sketch
const RESULT_URL = import.meta.env.VITE_TEST_RESULT_URL;

const handleTestComplete = async (finalResult) => {
  setResult(finalResult);
  setAppState("completed");

  if (!RESULT_URL) return;

  await fetch(RESULT_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      testId: config.id,
      completedAt: new Date().toISOString(),
      ...finalResult
    })
  });
};

Recommended Result Payload

{
  "testId": "frontend-essentials-test",
  "completedAt": "2026-03-14T00:00:00.000Z",
  "timedOut": false,
  "elapsedSec": 142,
  "secondsLeft": 218,
  "answers": {
    "js_scope": "let",
    "state_mutation": false,
    "promise_states": ["pending", "fulfilled", "rejected"]
  }
}

Optional Env Variables

  • VITE_TEST_CONFIG_URL — endpoint for loading test config
  • VITE_TEST_RESULT_URL — endpoint for posting final test result

📁 Project Structure

preact-threejs-3d-testing-lab/
├── index.html
├── netlify.toml
├── package.json
├── vite.config.js
├── tailwind.config.js
├── postcss.config.js
├── src/
│   ├── main.jsx
│   ├── App.jsx
│   ├── styles/
│   │   └── index.css
│   ├── data/
│   │   └── testData.js
│   └── components/
│       ├── ThreeBackground.jsx
│       ├── TestWizard.jsx
│       ├── ProgressBar.jsx
│       ├── StepIndicators.jsx
│       ├── QuestionRenderer.jsx
│       └── ResultsScreen.jsx
└── README.md

📱 Responsive Architecture

The Three.js canvas is position: fixed behind a naturally scrollable content layer.

  • ✅ No content clipping on small screens
  • ✅ Native mobile scroll behavior
  • ✅ Input-safe font sizing (16px) to prevent iOS zoom
  • ✅ Smooth touch + keyboard interaction

📄 License

MIT — use freely in personal and commercial projects.


Testing at light speed • Forged with Preact, Three.js & endless ☕

Releases

No releases published

Packages

 
 
 

Contributors