Add Signup/Signin page, query profile information via API and add footer with its respective subpages

This commit is contained in:
Oracle 2026-04-28 15:59:47 +02:00
parent 6596dc6d9b
commit c17d6b6b70
Signed by: Oracle
SSH key fingerprint: SHA256:x4/RtnjUyuHkdvmwNDsWSfcfF1V5PNr3OpriZqOvCX8
30 changed files with 1490 additions and 154 deletions

140
README.md
View file

@ -1,46 +1,116 @@
# Astro Starter Kit: Basics # PR Dojo
```sh A code review practice platform where users hunt for bugs in rejected PR code, earn XP, and submit fixes.
bun create astro@latest -- --template basics
## Features
- **Challenge-based learning** — Browse through real-world buggy code challenges
- **Interactive diff viewer** — Click on lines to mark them as buggy with visual feedback
- **Inline fix submission** — Write fixes per-marked line or as a unified diff
- **XP system** — Earn points for finding and fixing bugs
- **Difficulty ratings** — Challenges range from 1 to 5 stars
- **Hints system** — Hover over marked lines to reveal contextual hints
## Tech Stack
- **Astro 6** — Static site generator
- **Tailwind CSS v4** — Utility-first CSS via `@tailwindcss/vite`
- **TypeScript** — Strict mode
- **Node.js 22+** — Runtime
## Getting Started
### Prerequisites
- Node.js >= 22.12.0
- Bun package manager
### Installation
```bash
bun install
``` ```
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! ### Development
## 🚀 Project Structure ```bash
bun dev
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
│ └── favicon.svg
├── src
│   ├── assets
│   │   └── astro.svg
│   ├── components
│   │   └── Welcome.astro
│   ├── layouts
│   │   └── Layout.astro
│   └── pages
│   └── index.astro
└── package.json
``` ```
To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/). Starts the dev server at `localhost:4321`.
## 🧞 Commands ### Build
All commands are run from the root of the project, from a terminal: ```bash
bun build
```
| Command | Action | Produces a static site in `dist/`.
| :------------------------ | :----------------------------------------------- |
| `bun install` | Installs dependencies |
| `bun dev` | Starts local dev server at `localhost:4321` |
| `bun build` | Build your production site to `./dist/` |
| `bun preview` | Preview your build locally, before deploying |
| `bun astro ...` | Run CLI commands like `astro add`, `astro check` |
| `bun astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more? ### Preview
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). ```bash
bun preview
```
Locally previews the production build.
## Project Structure
```
src/
├── data/
│ └── challenges.json # Challenge data (code, hints, expected lines)
├── pages/
│ ├── index.astro # Challenge listing + hero
│ ├── profile.astro # User profile / XP dashboard
│ └── challenges/
│ └── [slug].astro # Single challenge view
├── components/
│ ├── DiffViewer.astro # Interactive diff viewer with line marking
│ └── Welcome.astro # Legacy starter component
├── layouts/
│ └── Layout.astro # Root layout with header/nav/footer
└── styles/
└── global.css # Global styles
```
## Challenge Data Format
Each challenge in `src/data/challenges.json` contains:
| Field | Type | Description |
|-------|------|-------------|
| `id` | number | Unique identifier |
| `title` | string | Challenge name |
| `repository` | string | GitHub repo path |
| `baseSha` | string | Base commit SHA |
| `difficulty` | number | 1-5 stars |
| `xpValue` | number | XP reward |
| `bugType` | string | Category (e.g., "Memory Safety") |
| `file` | string | Filename |
| `code` | string | Buggy source code |
| `hints` | string[] | Array of "Line N: description" hints |
| `expectedLines` | number[] | Lines containing bugs |
| `expectedPatch` | string | Unified diff of the fix |
## Development Notes
- Tailwind CSS v4 uses the Vite plugin — no `tailwind.config.js` or `@layer` directives
- Astro SSR renders everything client-side by default; add `client:*` directives only when interactivity is needed
- The `full_plan.md` documents the planned backend architecture (reference only, not a blind spec)
## Current Status
**Frontend complete, backend pending.**
The site currently runs as a static frontend with hardcoded challenge data. The planned backend will be built with either Quarkus or Node.js + SQLite to support:
- User authentication and XP tracking
- Persistent bug submissions
- Challenge progress tracking
- Leaderboards
## License
MIT

File diff suppressed because one or more lines are too long

1
dist/_astro/Layout.slfrh7tA.css vendored Normal file

File diff suppressed because one or more lines are too long

8
dist/about/index.html vendored Normal file
View file

@ -0,0 +1,8 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css"></head> <body data-astro-cid-sckkx6r4> <div class="max-w-4xl mx-auto px-4 py-16"> <h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">About PR Dojo</h1> <div class="space-y-6 text-[#8b949e] leading-relaxed"> <p> <strong class="text-[#c9d1d9]">PR Dojo</strong> is a practice platform for developers who want to sharpen their code review skills. We believe that the best way to become a better reviewer is by practicing on real-world code that didn't make it through review.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Our Mission</h2> <p>
Every day, thousands of pull requests are rejected, revised, or merged with bugs. These rejected PRs contain valuable learning opportunities — real bugs, real edge cases, real design mistakes. PR Dojo curates these mistakes into structured challenges where you can practice identifying issues and writing fixes.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">How It Works</h2> <div class="grid gap-4 mt-4"> <div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6"> <h3 class="text-[#58a6ff] font-semibold mb-2">1. Pick a Challenge</h3> <p class="text-sm">Browse our collection of buggy code snippets sourced from real rejected PRs. Each challenge includes a description, difficulty rating, and bug type categorization.</p> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6"> <h3 class="text-[#58a6ff] font-semibold mb-2">2. Find the Bugs</h3> <p class="text-sm">Review the code carefully, looking for logic errors, security vulnerabilities, performance issues, and style problems. Use hints if you get stuck.</p> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6"> <h3 class="text-[#58a6ff] font-semibold mb-2">3. Submit Your Fix</h3> <p class="text-sm">Write a patch that resolves the identified issues. Submit your fix and earn XP based on the difficulty and quality of your solution.</p> </div> </div> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Why Practice Code Review?</h2> <p>
Strong code review skills are one of the highest-leverage abilities a developer can have. Reviewing code helps you:
</p> <ul class="list-disc list-inside space-y-2 ml-4"> <li>Catch bugs before they reach production</li> <li>Learn new patterns and techniques from others' code</li> <li>Develop a critical eye for edge cases and security issues</li> <li>Improve your own writing by understanding what to avoid</li> <li>Communicate feedback constructively to teammates</li> </ul> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Bug Types We Cover</h2> <p>Our challenges span a wide range of common bug categories:</p> <div class="flex flex-wrap gap-2 mt-4"> <span class="bg-[#1f6feb]/20 text-[#58a6ff] border border-[#1f6feb]/40 rounded-full px-3 py-1 text-sm">Logic Errors</span> <span class="bg-[#f85149]/20 text-[#f85149] border border-[#f85149]/40 rounded-full px-3 py-1 text-sm">Security Vulnerabilities</span> <span class="bg-[#a371f7]/20 text-[#a371f7] border border-[#a371f7]/40 rounded-full px-3 py-1 text-sm">Performance Issues</span> <span class="bg-[#f2cc60]/20 text-[#f2cc60] border border-[#f2cc60]/40 rounded-full px-3 py-1 text-sm">Edge Cases</span> <span class="bg-[#388bfd]/20 text-[#388bfd] border border-[#388bfd]/40 rounded-full px-3 py-1 text-sm">Type Errors</span> <span class="bg-[#a5d6ff]/20 text-[#a5d6ff] border border-[#a5d6ff]/40 rounded-full px-3 py-1 text-sm">API Misuse</span> <span class="bg-[#8b949e]/20 text-[#8b949e] border border-[#8b949e]/40 rounded-full px-3 py-1 text-sm">Resource Leaks</span> </div> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Get Involved</h2> <p>
PR Dojo is an open project. If you have rejected PRs you'd like to contribute as challenges, or if you want to help build the platform, check out our GitHub repository and open an issue or pull request.
</p> </div> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html>

File diff suppressed because one or more lines are too long

View file

@ -1,8 +1,8 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css"> <!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px} <style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa].has-hint-color{color:#f85149!important}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa]:not(.bugged) span[data-astro-cid-2ex44dsa].line-number.has-hint-color{color:#8b949e!important;font-weight:400!important}.diff-viewer-container[data-astro-cid-2ex44dsa].revealed tr[data-astro-cid-2ex44dsa].bugged .hint[data-astro-cid-2ex44dsa]{display:inline}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) td[data-astro-cid-2ex44dsa]>span[data-astro-cid-2ex44dsa].has-hint-bg{background-color:transparent!important}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #2</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Null Pointer Dereference in User Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>expressjs/express</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>def5678</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Null Pointer</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+75 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-f5n3kgt" class="diff-viewer-container" data-dv-id="dv-f5n3kgt" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function getUserProfile(userId) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> const user = database.findUser(userId); </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> return { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> </style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #2</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Null Pointer Dereference in User Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>expressjs/express</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>def5678</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Null Pointer</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★☆☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+75 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-mry7iyc" class="diff-viewer-container" data-dv-id="dv-mry7iyc" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function getUserProfile(userId) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> const user = database.findUser(userId); </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> return { </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• No null check before accessing user properties </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> name: user.name, </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> email: user.email, </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> profile: user.profile.displayName </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> • No null check before accessing user properties </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> name: user.name, </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> email: user.email, </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> profile: user.profile.displayName </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Potential null pointer on user.profile </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> }; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 3: No null check before accessing user properties</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 6: Potential null pointer on user.profile</span> </li> </ul> </div> </div> </div> <script client:load> • Potential null pointer on user.profile </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> }; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> </div> </div> <script client:load>
(() => { (() => {
const el = document.querySelector('[data-dv-id]'); const el = document.querySelector('[data-dv-id]');
if (!el) return; if (!el) return;
@ -48,7 +48,7 @@ Or provide as unified diff:
Submit Fix Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg> </button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load> </button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html> <script client:load>
(() => { (() => {
const buggedLines = new Set(); const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container'); const fixEditorContainer = document.getElementById('fix-editor-container');
@ -180,10 +180,16 @@ Clear All
return; return;
} }
// Reveal bugged lines and hints in the diff viewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.add('revealed');
}
// Show success message // Show success message
if (submissionMessage) { if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)'; submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]'; submissionMessage.className = 'mt-3 text-sm text-[#3ac840]';
submissionMessage.classList.remove('hidden'); submissionMessage.classList.remove('hidden');
} }
}); });
@ -199,7 +205,11 @@ Clear All
if (submissionMessage) submissionMessage.classList.add('hidden'); if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI(); updateUI();
// Reset bugged state in DiffViewer // Reset bugged state and revealed state in DiffViewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.remove('revealed');
}
document.querySelectorAll('tr.bugged').forEach(row => { document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged'); row.classList.remove('bugged');
}); });

View file

@ -1,9 +1,9 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css"> <!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px} <style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa].has-hint-color{color:#f85149!important}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa]:not(.bugged) span[data-astro-cid-2ex44dsa].line-number.has-hint-color{color:#8b949e!important;font-weight:400!important}.diff-viewer-container[data-astro-cid-2ex44dsa].revealed tr[data-astro-cid-2ex44dsa].bugged .hint[data-astro-cid-2ex44dsa]{display:inline}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) td[data-astro-cid-2ex44dsa]>span[data-astro-cid-2ex44dsa].has-hint-bg{background-color:transparent!important}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #3</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Race Condition in Counter Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>rails/rails</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>ghi9012</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Concurrency</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+150 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-mubau9j" class="diff-viewer-container" data-dv-id="dv-mubau9j" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> class Counter { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> </style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #3</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Race Condition in Counter Service</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>rails/rails</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>ghi9012</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Concurrency</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+150 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-nrbzznj" class="diff-viewer-container" data-dv-id="dv-nrbzznj" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> class Counter { </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Line 12: Same issue with decrement operation </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> constructor() { </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> this.value = 0; </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> increment() { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value + 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> • Line 12: Same issue with decrement operation </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> constructor() { </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> this.value = 0; </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> increment() { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value + 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Race condition when increment called concurrently </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="10" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 10 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 10 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="11" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 11 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 11 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> decrement() { </span> </td> </tr><tr data-line="12" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 12 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 12 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value - 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> • Race condition when increment called concurrently </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="10" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 10 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 10 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="11" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 11 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 11 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> decrement() { </span> </td> </tr><tr data-line="12" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 12 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 12 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> this.value = this.value - 1; </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Same issue with decrement operation </span> </td> </tr><tr data-line="13" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 13 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 13 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="14" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 14 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 14 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="15" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 15 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 15 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 7: Race condition when increment called concurrently</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 12: Same issue with decrement operation</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>No synchronization mechanism for thread safety</span> </li> </ul> </div> </div> </div> <script client:load> • Same issue with decrement operation </span> </td> </tr><tr data-line="13" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 13 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 13 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return this.value; </span> </td> </tr><tr data-line="14" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 14 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 14 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="15" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 15 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 15 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> </div> </div> <script client:load>
(() => { (() => {
const el = document.querySelector('[data-dv-id]'); const el = document.querySelector('[data-dv-id]');
if (!el) return; if (!el) return;
@ -49,7 +49,7 @@ Or provide as unified diff:
Submit Fix Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg> </button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load> </button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html> <script client:load>
(() => { (() => {
const buggedLines = new Set(); const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container'); const fixEditorContainer = document.getElementById('fix-editor-container');
@ -181,10 +181,16 @@ Clear All
return; return;
} }
// Reveal bugged lines and hints in the diff viewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.add('revealed');
}
// Show success message // Show success message
if (submissionMessage) { if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)'; submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]'; submissionMessage.className = 'mt-3 text-sm text-[#3ac840]';
submissionMessage.classList.remove('hidden'); submissionMessage.classList.remove('hidden');
} }
}); });
@ -200,7 +206,11 @@ Clear All
if (submissionMessage) submissionMessage.classList.add('hidden'); if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI(); updateUI();
// Reset bugged state in DiffViewer // Reset bugged state and revealed state in DiffViewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.remove('revealed');
}
document.querySelectorAll('tr.bugged').forEach(row => { document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged'); row.classList.remove('bugged');
}); });

View file

@ -1,8 +1,8 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css"> <!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px} <style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa].has-hint-color{color:#f85149!important}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa]:not(.bugged) span[data-astro-cid-2ex44dsa].line-number.has-hint-color{color:#8b949e!important;font-weight:400!important}.diff-viewer-container[data-astro-cid-2ex44dsa].revealed tr[data-astro-cid-2ex44dsa].bugged .hint[data-astro-cid-2ex44dsa]{display:inline}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) td[data-astro-cid-2ex44dsa]>span[data-astro-cid-2ex44dsa].has-hint-bg{background-color:transparent!important}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #4</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>SQL Injection Vulnerability</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★★</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>django/django</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>jkl3456</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Security</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★★</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+200 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-ikbmd7r" class="diff-viewer-container" data-dv-id="dv-ikbmd7r" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function findUserByUsername(username) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `SELECT * FROM users WHERE username = &#39;${username}&#39;`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> </style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #4</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>SQL Injection Vulnerability</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★★★</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>django/django</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>jkl3456</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Security</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★★★</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+200 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-e35geif" class="diff-viewer-container" data-dv-id="dv-e35geif" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function findUserByUsername(username) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `SELECT * FROM users WHERE username = &#39;${username}&#39;`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Direct string interpolation allows SQL injection </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function deleteUser(userId) { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `DELETE FROM users WHERE id = ${userId}`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> • Direct string interpolation allows SQL injection </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function deleteUser(userId) { </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> const query = `DELETE FROM users WHERE id = ${userId}`; </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Second SQL injection vulnerability in delete operation </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 2: Direct string interpolation allows SQL injection</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 7: Second SQL injection vulnerability in delete operation</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Should use parameterized queries instead</span> </li> </ul> </div> </div> </div> <script client:load> • Second SQL injection vulnerability in delete operation </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return database.query(query); </span> </td> </tr><tr data-line="9" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 9 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 9 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> </div> </div> <script client:load>
(() => { (() => {
const el = document.querySelector('[data-dv-id]'); const el = document.querySelector('[data-dv-id]');
if (!el) return; if (!el) return;
@ -48,7 +48,7 @@ Or provide as unified diff:
Submit Fix Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg> </button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load> </button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html> <script client:load>
(() => { (() => {
const buggedLines = new Set(); const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container'); const fixEditorContainer = document.getElementById('fix-editor-container');
@ -180,10 +180,16 @@ Clear All
return; return;
} }
// Reveal bugged lines and hints in the diff viewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.add('revealed');
}
// Show success message // Show success message
if (submissionMessage) { if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)'; submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]'; submissionMessage.className = 'mt-3 text-sm text-[#3ac840]';
submissionMessage.classList.remove('hidden'); submissionMessage.classList.remove('hidden');
} }
}); });
@ -199,7 +205,11 @@ Clear All
if (submissionMessage) submissionMessage.classList.add('hidden'); if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI(); updateUI();
// Reset bugged state in DiffViewer // Reset bugged state and revealed state in DiffViewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.remove('revealed');
}
document.querySelectorAll('tr.bugged').forEach(row => { document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged'); row.classList.remove('bugged');
}); });

View file

@ -1,8 +1,8 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css"> <!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css">
<style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px} <style>tr[data-astro-cid-2ex44dsa]{line-height:1.6}tr[data-astro-cid-2ex44dsa]:hover{background-color:#161b22}td[data-astro-cid-2ex44dsa]:first-child{border-right:1px solid #30363d}tr[data-astro-cid-2ex44dsa].bugged td[data-astro-cid-2ex44dsa]:first-child{border-left:3px solid #f85149}tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa]{color:#f85149}tr[data-astro-cid-2ex44dsa].bugged{background-color:#f8514918}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa].bugged .line-number[data-astro-cid-2ex44dsa].has-hint-color{color:#f85149!important}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) tr[data-astro-cid-2ex44dsa]:not(.bugged) span[data-astro-cid-2ex44dsa].line-number.has-hint-color{color:#8b949e!important;font-weight:400!important}.diff-viewer-container[data-astro-cid-2ex44dsa].revealed tr[data-astro-cid-2ex44dsa].bugged .hint[data-astro-cid-2ex44dsa]{display:inline}.diff-viewer-container[data-astro-cid-2ex44dsa]:not(.revealed) td[data-astro-cid-2ex44dsa]>span[data-astro-cid-2ex44dsa].has-hint-bg{background-color:transparent!important}#fix-editor-container[data-astro-cid-7fgtuneg] textarea[data-astro-cid-7fgtuneg]{resize:vertical;min-height:48px}
</style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #5</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Infinite Loop in Data Processing</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>angular/angular</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>mno7890</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Logic Error</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+100 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-qgi8fcj" class="diff-viewer-container" data-dv-id="dv-qgi8fcj" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function processItems(items) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> let index = 0; </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> while (index &lt; items.length) { </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> </style></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen" data-astro-cid-7fgtuneg> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]" data-astro-cid-7fgtuneg> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center gap-4" data-astro-cid-7fgtuneg> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>← Back to Challenges</a> <span class="text-[#8b949e] text-sm" data-astro-cid-7fgtuneg>/ Challenge #5</span> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6" data-astro-cid-7fgtuneg> <!-- Challenge Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6" data-astro-cid-7fgtuneg> <div class="flex justify-between items-start mb-4" data-astro-cid-7fgtuneg> <div data-astro-cid-7fgtuneg> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2 no-underline" data-astro-cid-7fgtuneg>Infinite Loop in Data Processing</h1> <div class="mb-4" data-astro-cid-7fgtuneg> <span class="text-[#79c0ff] text-xl no-underline" data-astro-cid-7fgtuneg>★★★☆☆</span> </div> <div class="flex flex-wrap items-center gap-4 text-sm" data-astro-cid-7fgtuneg> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>📁 <span class="text-[#a5d6ff]" data-astro-cid-7fgtuneg>angular/angular</span></span> <span class="text-[#8b949e]" data-astro-cid-7fgtuneg>🔢 <code class="bg-[#21262d] px-2 py-0.5 rounded text-xs" data-astro-cid-7fgtuneg>mno7890</code></span> <span class="px-2 py-0.5 bg-[#0a3064] rounded text-xs text-[#79c0ff]" data-astro-cid-7fgtuneg>Logic Error</span> </div> </div> <div class="text-right" data-astro-cid-7fgtuneg> <div class="text-[#79c0ff] text-lg mb-1 no-underline" data-astro-cid-7fgtuneg>★★★☆☆</div> <div class="text-xs text-[#8b949e]" data-astro-cid-7fgtuneg>Difficulty</div> </div> </div> <div class="bg-[#0a3064] rounded p-3 flex items-center gap-2" data-astro-cid-7fgtuneg> <svg class="w-5 h-5 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20" data-astro-cid-7fgtuneg> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" data-astro-cid-7fgtuneg></path> </svg> <span class="text-[#79c0ff] font-semibold" data-astro-cid-7fgtuneg>+100 XP reward</span> </div> </div> <!-- Code Viewer --> <div id="dv-7zziy6d" class="diff-viewer-container" data-dv-id="dv-7zziy6d" data-astro-cid-2ex44dsa> <div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden" data-astro-cid-2ex44dsa> <div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]" data-astro-cid-2ex44dsa> <span class="text-sm text-[#8b949e]" data-astro-cid-2ex44dsa>file.js</span> </div> <div class="overflow-x-auto" data-astro-cid-2ex44dsa> <table class="w-full font-mono text-sm" data-astro-cid-2ex44dsa> <tbody data-astro-cid-2ex44dsa> <tr data-line="1" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 1 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 1 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> function processItems(items) { </span> </td> </tr><tr data-line="2" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 2 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 2 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> let index = 0; </span> </td> </tr><tr data-line="3" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 3 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 3 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> while (index &lt; items.length) { </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Line 3-6: Infinite loop - index never increments </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> console.log(items[index]); </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number text-[#ff7b72] font-semibold" data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> // Missing: index++ </span> <span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity" data-astro-cid-2ex44dsa> • Line 3-6: Infinite loop - index never increments </span> </td> </tr><tr data-line="4" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 4 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 4 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> console.log(items[index]); </span> </td> </tr><tr data-line="5" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 5 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number has-hint-color" data-astro-cid-2ex44dsa> 5 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] has-hint-bg bg-[#ff7b7233] px-2 -mx-2" data-astro-cid-2ex44dsa> // Missing: index++ </span> <span class="ml-2 hint text-[#79c0ff] text-xs hidden" data-astro-cid-2ex44dsa>
• Missing increment statement causes endless iteration </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return &#39;done&#39;; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> <div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]" data-astro-cid-2ex44dsa> <h3 class="text-sm font-semibold text-[#c9d1d9] mb-2" data-astro-cid-2ex44dsa>Hints:</h3> <ul class="space-y-1" data-astro-cid-2ex44dsa> <li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 3-6: Infinite loop - index never increments</span> </li><li class="text-sm text-[#8b949e] flex items-start gap-2" data-astro-cid-2ex44dsa> <span class="text-[#79c0ff] mt-0.5" data-astro-cid-2ex44dsa></span> <span data-astro-cid-2ex44dsa>Line 5: Missing increment statement causes endless iteration</span> </li> </ul> </div> </div> </div> <script client:load> • Missing increment statement causes endless iteration </span> </td> </tr><tr data-line="6" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 6 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 6 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr><tr data-line="7" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 7 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 7 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> return &#39;done&#39;; </span> </td> </tr><tr data-line="8" class="group hover:bg-[#161b22] cursor-pointer transition-colors" title="Mark line 8 as buggy" data-astro-cid-2ex44dsa> <td class="w-16 text-right pr-4 select-none" data-astro-cid-2ex44dsa> <span class="text-[#8b949e] line-number " data-astro-cid-2ex44dsa> 8 </span> </td> <td class="px-4 py-0.5 whitespace-pre" data-astro-cid-2ex44dsa> <span class="text-[#e6edf3] " data-astro-cid-2ex44dsa> } </span> </td> </tr> </tbody> </table> </div> </div> </div> <script client:load>
(() => { (() => {
const el = document.querySelector('[data-dv-id]'); const el = document.querySelector('[data-dv-id]');
if (!el) return; if (!el) return;
@ -48,7 +48,7 @@ Or provide as unified diff:
Submit Fix Submit Fix
</button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg> </button> <button id="clear-all-btn" class="bg-[#21262d] hover:bg-[#30363d] text-[#8b949e] font-semibold py-2.5 px-4 rounded-md transition-colors focus:outline-none focus:ring-0" data-astro-cid-7fgtuneg>
Clear All Clear All
</button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> </body></html> <script client:load> </button> </div> <div id="submission-message" class="mt-3 text-sm hidden" data-astro-cid-7fgtuneg></div> </div> </div> </main> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html> <script client:load>
(() => { (() => {
const buggedLines = new Set(); const buggedLines = new Set();
const fixEditorContainer = document.getElementById('fix-editor-container'); const fixEditorContainer = document.getElementById('fix-editor-container');
@ -180,10 +180,16 @@ Clear All
return; return;
} }
// Reveal bugged lines and hints in the diff viewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.add('revealed');
}
// Show success message // Show success message
if (submissionMessage) { if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)'; submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]'; submissionMessage.className = 'mt-3 text-sm text-[#3ac840]';
submissionMessage.classList.remove('hidden'); submissionMessage.classList.remove('hidden');
} }
}); });
@ -199,7 +205,11 @@ Clear All
if (submissionMessage) submissionMessage.classList.add('hidden'); if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI(); updateUI();
// Reset bugged state in DiffViewer // Reset bugged state and revealed state in DiffViewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.remove('revealed');
}
document.querySelectorAll('tr.bugged').forEach(row => { document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged'); row.classList.remove('bugged');
}); });

1
dist/faq/index.html vendored Normal file

File diff suppressed because one or more lines are too long

7
dist/imprint/index.html vendored Normal file
View file

@ -0,0 +1,7 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css"></head> <body data-astro-cid-sckkx6r4> <div class="max-w-4xl mx-auto px-4 py-16"> <h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">Imprint</h1> <div class="space-y-6 text-[#8b949e] leading-relaxed"> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Information according to § 5 TMG</h2> <div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6 space-y-3"> <p><strong class="text-[#c9d1d9]">PR Dojo</strong></p> <p>Project Lead: [Your Name]</p> <p>Address: [Your Address]</p> <p>[Postal Code] [City]</p> <p>[Country]</p> </div> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Contact</h2> <div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6 space-y-2"> <p>Email: [contact@prdojo.example.com](mailto:contact@prdojo.example.com)</p> <p>GitHub: [github.com/prdojo](https://github.com/prdojo)</p> </div> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Disclaimer</h2> <p>
The content on this site is provided with the utmost care. However, we do not guarantee the accuracy, completeness, or timeliness of any content on this website.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Links and References</h2> <p>
This website contains links to external websites of third parties, over whose content we have no influence. Therefore, we cannot assume any liability for these external contents. The respective providers or operators of the pages are always responsible for the content of the linked pages.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Copyright</h2> <p>
The content and works created by us on these pages are subject to German and international copyright laws. Any duplication, processing, distribution, or any form of utilization beyond the scope of copyright law shall require the prior written consent of its respective author or creator. Downloads and copies of this site are only permitted for private, non-commercial use.
</p> </div> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html>

8
dist/index.html vendored

File diff suppressed because one or more lines are too long

18
dist/privacy/index.html vendored Normal file
View file

@ -0,0 +1,18 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css"></head> <body data-astro-cid-sckkx6r4> <div class="max-w-4xl mx-auto px-4 py-16"> <h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">Privacy Policy</h1> <div class="space-y-6 text-[#8b949e] leading-relaxed"> <p>
Last updated: <strong class="text-[#c9d1d9]">April 28, 2026</strong> </p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Introduction</h2> <p>
PR Dojo ("we", "our", or "us") is committed to protecting your privacy. This Privacy Policy explains how your personal information is collected, used, and disclosed by PR Dojo.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Information We Collect</h2> <p>
PR Dojo is currently in its early stages and does not collect personal data beyond what is necessary for functionality. As we grow, we may collect:
</p> <ul class="list-disc list-inside space-y-2 ml-4"> <li><strong class="text-[#c9d1d9]">Account Data:</strong> When you create a profile, we may collect a username and email address.</li> <li><strong class="text-[#c9d1d9]">Usage Data:</strong> We may log which challenges you've completed and your XP score to track progress.</li> <li><strong class="text-[#c9d1d9]">Technical Data:</strong> Standard server logs may record IP addresses, browser type, and access times for security and analytics purposes.</li> </ul> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">How We Use Your Information</h2> <p>We use collected information to:</p> <ul class="list-disc list-inside space-y-2 ml-4"> <li>Provide and improve our services</li> <li>Track your progress and XP scores</li> <li>Communicate with you about updates or changes</li> <li>Ensure the security of our platform</li> </ul> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Data Sharing</h2> <p>
We do not sell, trade, or rent your personal information to third parties. We may share data only in the following circumstances:
</p> <ul class="list-disc list-inside space-y-2 ml-4"> <li>With your explicit consent</li> <li>To comply with legal obligations</li> <li>With service providers who assist in operating our platform (under confidentiality agreements)</li> </ul> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Data Security</h2> <p>
We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Your Rights</h2> <p>Depending on your jurisdiction, you may have the right to:</p> <ul class="list-disc list-inside space-y-2 ml-4"> <li>Access your personal data</li> <li>Request correction of inaccurate data</li> <li>Request deletion of your data</li> <li>Object to or restrict processing of your data</li> <li>Data portability</li> </ul> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Cookies</h2> <p>
PR Dojo may use cookies and similar technologies for functionality, analytics, and personalization. You can control cookie settings through your browser preferences.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Children's Privacy</h2> <p>
PR Dojo is not intended for children under 13. We do not knowingly collect personal information from children under 13.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Changes to This Policy</h2> <p>
We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new policy on this page and updating the "last updated" date.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Contact Us</h2> <p>
If you have questions about this Privacy Policy, please contact us at:
</p> <p><a href="mailto:privacy@prdojo.example.com" class="text-[#58a6ff] hover:text-[#79c0ff]">privacy@prdojo.example.com</a></p> </div> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html>

View file

@ -1,3 +1,258 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.D3ovbguN.css"></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen"> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]"> <div class="max-w-6xl mx-auto px-4 py-3"> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">← Back to Challenges</a> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6"> <!-- Profile Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center gap-6"> <div class="w-20 h-20 bg-[#21262d] rounded-full flex items-center justify-center"> <svg class="w-12 h-12 text-[#8b949e]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path> </svg> </div> <div> <h1 class="text-2xl font-semibold text-[#c9d1d9]">Anonymous User</h1> <p class="text-[#8b949e] text-sm mt-1">Code Review Practitioner</p> </div> </div> </div> <!-- Stats Grid --> <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"></path> </svg> <span class="text-[#8b949e] text-sm">Total XP</span> </div> <div class="text-3xl font-bold text-[#58a6ff]">0</div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#a5d6ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Challenges Solved</span> </div> <div class="text-3xl font-bold text-[#c9d1d9]">0<span class="text-[#8b949e] text-xl">/5</span></div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#79c0ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Current Streak</span> </div> <div class="text-3xl font-bold text-[#79c0ff]">0<span class="text-[#8b949e] text-xl"> days</span></div> </div> </div> <!-- Rank Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center justify-between mb-3"> <span class="text-[#c9d1d9] font-semibold">Current Rank: Novice</span> <span class="text-[#8b949e] text-sm">0 / 500 XP</span> </div> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div class="h-full bg-[#58a6ff] w-0"></div> </div> <div class="mt-4 flex items-center justify-between"> <span class="text-[#c9d1d9] font-semibold">Next: Apprentice</span> <span class="text-[#8b949e] text-sm">500 / 1500 XP</span> </div> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div class="h-full bg-[#a5d6ff] w-0"></div> </div> </div> <!-- Recent Activity --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6"> <h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Recent Activity</h2> <div class="text-center text-[#8b949e] py-8"> <svg class="w-12 h-12 mx-auto mb-3 text-[#30363d]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <p>No challenges completed yet</p> <a href="/challenges/1" class="text-[#58a6ff] mt-2 inline-block no-underline"> <!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css"></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen"> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]"> <div class="max-w-6xl mx-auto px-4 py-3"> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">← Back to Challenges</a> </div> </header> <main class="max-w-6xl mx-auto px-4 py-6"> <!-- Profile Header --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center gap-6"> <div class="w-20 h-20 bg-[#21262d] rounded-full flex items-center justify-center"> <svg id="avatar-placeholder" class="w-12 h-12 text-[#8b949e]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path> </svg> </div> <div> <h1 id="profile-name" class="text-2xl font-semibold text-[#c9d1d9]">Loading...</h1> <p id="profile-title" class="text-[#8b949e] text-sm mt-1">Code Review Practitioner</p> </div> </div> </div> <!-- Stats Grid --> <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"></path> </svg> <span class="text-[#8b949e] text-sm">Total XP</span> </div> <div id="stat-xp" class="text-3xl font-bold text-[#58a6ff]">?</div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#a5d6ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Challenges Solved</span> </div> <div id="stat-solved" class="text-3xl font-bold text-[#c9d1d9]">?/<span class="text-[#8b949e] text-xl">?</span></div> </div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="flex items-center gap-3 mb-2"> <svg class="w-6 h-6 text-[#79c0ff]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <span class="text-[#8b949e] text-sm">Current Streak</span> </div> <div id="stat-streak" class="text-3xl font-bold text-[#79c0ff]">?<span class="text-[#8b949e] text-xl"> days</span></div> </div> </div> <!-- Rank Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="flex items-center justify-between mb-3"> <span id="rank-label" class="text-[#c9d1d9] font-semibold">Loading rank...</span> <span id="rank-xp" class="text-[#8b949e] text-sm">? / ? XP</span> </div> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div id="rank-bar" class="h-full bg-[#58a6ff] w-0 transition-all duration-500"></div> </div> </div> <!-- Badges --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Badges</h2> <div id="badge-list" class="flex flex-wrap gap-3"> <div class="text-center text-[#8b949e] py-4"> <p>Loading badges...</p> </div> </div> </div> <!-- Recent Activity --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6"> <h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Recent Activity</h2> <div id="activity-list" class="space-y-3"> <div class="text-center text-[#8b949e] py-8"> <svg class="w-12 h-12 mx-auto mb-3 text-[#30363d]" fill="currentColor" viewBox="0 0 20 20"> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"></path> </svg> <p>No challenges completed yet</p> <a href="/challenges/1" class="text-[#58a6ff] mt-2 inline-block no-underline">
Start your first challenge → Start your first challenge →
</a> </div> </div> </main> </div> </body></html> </a> </div> </div> </div> </main> </div> <script client:load>
(function() {
var API_BASE = 'http://localhost:9090/api';
// Fallback rank tiers if API doesn't provide rank info
var rankTiers = [
{ name: 'Novice', xpThreshold: 0 },
{ name: 'Apprentice', xpThreshold: 500 },
{ name: 'Skilled', xpThreshold: 1500 },
{ name: 'Expert', xpThreshold: 3000 },
{ name: 'Master', xpThreshold: 5000 },
];
function getNextTier(currentThreshold) {
for (var i = 0; i < rankTiers.length; i++) {
if (rankTiers[i].xpThreshold > currentThreshold) {
return rankTiers[i];
}
}
return null;
}
function setWidth(el, pct) {
if (!el) return;
el.style.width = Math.min(100, Math.max(0, pct)) + '%';
}
function loadProfile() {
fetch(API_BASE + '/me')
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(user) {
// Profile header
var nameEl = document.getElementById('profile-name');
var titleEl = document.getElementById('profile-title');
if (nameEl) nameEl.textContent = user.username || user.name || 'Anonymous User';
if (titleEl) titleEl.textContent = user.title || 'Code Review Practitioner';
if (user.avatarUrl) {
var avatarEl = document.getElementById('avatar-placeholder');
if (avatarEl) {
var img = document.createElement('img');
img.src = user.avatarUrl;
img.alt = user.username || 'Avatar';
img.className = 'w-20 h-20 rounded-full object-cover';
avatarEl.replaceWith(img);
}
}
// Badges
var badges = user.badges || user.badge_list || [];
var badgeList = document.getElementById('badge-list');
if (badgeList) {
if (badges.length === 0) {
badgeList.innerHTML = '<div class="text-center text-[#8b949e] py-4"><p>No badges earned yet</p></div>';
} else {
// Tier colors and icons
var tierMap = {
'Beginner': { icon: '🌱', color: '#3fb950' },
'Intermediate': { icon: '🔧', color: '#58a6ff' },
'Advanced': { icon: '⚡', color: '#d29922' },
'Expert': { icon: '🏆', color: '#f85149' },
'Hard Mode': { icon: '💀', color: '#a371f7' },
'Legendary': { icon: '👑', color: '#f778ba' },
};
var defaultTier = { icon: '🏅', color: '#8b949e' };
fetch(API_BASE + '/badges')
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(data) {
var definitions = data.definitions || data.badges || [];
var badgeMap = {};
for (var i = 0; i < definitions.length; i++) {
var def = definitions[i];
var tierInfo = def.tier || {};
var tierName = tierInfo.level || tierInfo.name || '';
var tierData = tierMap[tierName] || defaultTier;
badgeMap[def.id] = {
name: def.title || def.id,
icon: tierData.icon,
desc: def.description || '',
color: tierData.color,
};
}
badgeList.innerHTML = '';
for (var b = 0; b < badges.length; b++) {
var badgeId = badges[b];
var meta = badgeMap[badgeId] || { name: badgeId, icon: '🏅', desc: 'A badge earned in challenges', color: '#8b949e' };
var badgeEl = document.createElement('div');
badgeEl.className = 'flex items-center gap-3 bg-[#21262d] border border-[#30363d] rounded-md px-4 py-3 min-w-[200px]';
badgeEl.title = meta.desc;
badgeEl.innerHTML = '<div class="w-10 h-10 rounded-full flex items-center justify-center text-xl" style="background-color:' + meta.color + '20">' + meta.icon + '</div><div><p class="text-[#c9d1d9] text-sm font-medium">' + meta.name + '</p><p class="text-[#8b949e] text-xs">' + meta.desc + '</p></div>';
badgeList.appendChild(badgeEl);
}
})
.catch(function(err) {
console.error('Failed to load badge definitions:', err);
var fallbackMeta = {
'first-bug': { name: 'First Bug', icon: '🐛', desc: 'Found your first bug', color: '#3fb950' },
'rapid-solver': { name: 'Rapid Solver', icon: '⚡', desc: 'Solved 10 challenges quickly', color: '#d29922' },
'streak-master': { name: 'Streak Master', icon: '🔥', desc: '7-day streak achieved', color: '#f85149' },
'xp-hunter': { name: 'XP Hunter', icon: '⭐', desc: 'Accumulated 1000 XP', color: '#58a6ff' },
'bug-slayer': { name: 'Bug Slayer', icon: '⚔️', desc: 'Solved 50 challenges', color: '#a371f7' },
'perfect-patch': { name: 'Perfect Patch', icon: '🎯', desc: 'Submitted a perfect patch', color: '#f778ba' },
'code-reviewer': { name: 'Code Reviewer', icon: '📋', desc: 'Completed first code review', color: '#79c0ff' },
'champion': { name: 'Champion', icon: '🏆', desc: 'Reached Master rank', color: '#d29922' },
};
badgeList.innerHTML = '';
for (var b = 0; b < badges.length; b++) {
var badgeId = badges[b];
var meta = fallbackMeta[badgeId] || { name: badgeId, icon: '🏅', desc: 'A badge earned in challenges', color: '#8b949e' };
var badgeEl = document.createElement('div');
badgeEl.className = 'flex items-center gap-3 bg-[#21262d] border border-[#30363d] rounded-md px-4 py-3 min-w-[200px]';
badgeEl.title = meta.desc;
badgeEl.innerHTML = '<div class="w-10 h-10 rounded-full flex items-center justify-center text-xl" style="background-color:' + meta.color + '20">' + meta.icon + '</div><div><p class="text-[#c9d1d9] text-sm font-medium">' + meta.name + '</p><p class="text-[#8b949e] text-xs">' + meta.desc + '</p></div>';
badgeList.appendChild(badgeEl);
}
});
}
}
// Stats - try snake_case first (API format), then camelCase fallbacks
var xp = user.total_xp != null ? user.total_xp : (user.xp != null ? user.xp : (user.totalXP != null ? user.totalXP : 0));
var solved = user.solved_count != null ? user.solved_count : (user.challengesSolved != null ? user.challengesSolved : (user.solvedCount != null ? user.solvedCount : 0));
var streak = user.streak != null ? user.streak : (user.current_streak != null ? user.current_streak : (user.currentStreak != null ? user.currentStreak : 0));
// Fetch total challenges dynamically from API
var totalChallenges = user.total_challenges != null ? user.total_challenges : (user.totalChallenges != null ? user.totalChallenges : null);
if (totalChallenges == null) {
fetch(API_BASE + '/challenges/totalamount')
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(data) {
var total = typeof data === 'number' ? data : (data.total_amount != null ? data.total_amount : (data.totalAmount != null ? data.totalAmount : (data.total != null ? data.total : '?')));
var solvedEl = document.getElementById('stat-solved');
if (solvedEl) solvedEl.innerHTML = solved + '<span class="text-[#8b949e] text-xl">/' + total + '</span>';
})
.catch(function(err) {
console.error('Failed to load total challenges:', err);
var solvedEl = document.getElementById('stat-solved');
if (solvedEl) solvedEl.innerHTML = solved + '<span class="text-[#8b949e] text-xl">/?</span>';
});
} else {
// totalChallenges already set from /me response
}
var xpEl = document.getElementById('stat-xp');
if (xpEl) xpEl.textContent = String(xp);
var solvedEl = document.getElementById('stat-solved');
if (solvedEl) solvedEl.innerHTML = solved + '<span class="text-[#8b949e] text-xl">/' + totalChallenges + '</span>';
var streakEl = document.getElementById('stat-streak');
if (streakEl) streakEl.innerHTML = streak + '<span class="text-[#8b949e] text-xl"> days</span>';
// Rank - use API rank object if available, otherwise compute from tiers
var rankLabelEl = document.getElementById('rank-label');
var rankXpEl = document.getElementById('rank-xp');
var rankBar = document.getElementById('rank-bar');
if (user.rank && user.rank.title) {
// API provides rank object
var apiRank = user.rank;
var rankTitle = apiRank.title;
var currentXp = apiRank.current_xp != null ? apiRank.current_xp : xp;
var currentThreshold = apiRank.xp_threshold != null ? apiRank.xp_threshold : 0;
// Find next tier from fallback list
var nextTier = getNextTier(currentThreshold);
if (nextTier) {
var range = nextTier.xpThreshold - currentThreshold;
var pct = range > 0 ? ((currentXp - currentThreshold) / range) * 100 : 100;
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + rankTitle + ' \u2192 Next: ' + nextTier.name;
if (rankXpEl) rankXpEl.textContent = currentXp + ' / ' + nextTier.xpThreshold + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, pct);
}
} else {
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + rankTitle;
if (rankXpEl) rankXpEl.textContent = currentXp + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, 100);
}
}
} else {
// No rank from API, compute from tiers
var currentTier = rankTiers[0];
var nextTier = rankTiers[1];
for (var i = rankTiers.length - 1; i >= 0; i--) {
if (xp >= rankTiers[i].xpThreshold) {
currentTier = rankTiers[i];
nextTier = rankTiers[i + 1] || null;
break;
}
}
if (nextTier) {
var range = nextTier.xpThreshold - currentTier.xpThreshold;
var pct = range > 0 ? ((xp - currentTier.xpThreshold) / range) * 100 : 100;
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + currentTier.name + ' \u2192 Next: ' + nextTier.name;
if (rankXpEl) rankXpEl.textContent = xp + ' / ' + nextTier.xpThreshold + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, pct);
}
} else {
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + currentTier.name;
if (rankXpEl) rankXpEl.textContent = xp + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, 100);
}
}
}
// Recent Activity
var activities = user.recentActivity || user.activity || [];
var activityList = document.getElementById('activity-list');
if (activityList && activities.length > 0) {
activityList.innerHTML = '';
for (var a = 0; a < activities.length && a < 10; a++) {
var activity = activities[a];
var item = document.createElement('div');
item.className = 'flex items-center gap-3 p-3 bg-[#21262d] rounded-md';
var isSolved = activity.type === 'solved' || activity.type === 'completed';
var checkSvg = '<svg class="w-5 h-5 text-[#3fb950] flex-shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
var infoSvg = '<svg class="w-5 h-5 text-[#58a6ff] flex-shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd"/></svg>';
var xpHtml = activity.xpGained ? '<span class="text-[#58a6ff] text-sm font-medium">+' + activity.xpGained + ' XP</span>' : '';
item.innerHTML = (isSolved ? checkSvg : infoSvg) + '<div class="flex-1 min-w-0"><p class="text-[#c9d1d9] text-sm truncate">' + (activity.title || activity.description || 'Challenge completed') + '</p><p class="text-[#8b949e] text-xs">' + (activity.date || activity.timestamp || '') + '</p></div>' + xpHtml;
activityList.appendChild(item);
}
}
})
.catch(function(err) {
console.error('Failed to load profile:', err);
var nameEl = document.getElementById('profile-name');
if (nameEl) nameEl.textContent = 'Anonymous User';
var activityList = document.getElementById('activity-list');
if (activityList) {
activityList.innerHTML = '<div class="text-center text-[#f85149] py-4">Failed to load profile data. Is the API running?</div>';
}
});
}
loadProfile();
})();
</script> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html>

11
dist/signin/index.html vendored Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css"></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen bg-[#0d1117]"> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]"> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between"> <a href="/" class="flex items-center gap-3 no-underline focus:outline-none focus:ring-0"> <svg class="w-8 h-8 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 2a8 8 0 100 16 8 8 0 000-16zm1 11H9v-2h2v2zm0-4H9V5h2v4z"></path> </svg> <span class="text-xl font-semibold text-[#c9d1d9]">PR Dojo</span> </a> <nav class="flex items-center gap-4"> <a href="/" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Home</a> </nav> </div> </header> <main class="flex items-center justify-center px-4 py-16"> <div class="w-full max-w-md"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-8"> <div class="text-center mb-8"> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2">Sign in to PR Dojo</h1> <p class="text-[#8b949e] text-sm">Continue your code review practice</p> </div> <form on:submit|preventDefault="(e) => {
e.preventDefault();
alert(&#34;Sign in is not available yet. Backend API is not implemented.&#34;);
}"> <!-- Email --> <div class="mb-4"> <label for="email" class="block text-[#c9d1d9] text-sm font-medium mb-2">Email</label> <input type="email" id="email" name="email" required placeholder="you@example.com" class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"> </div> <!-- Password --> <div class="mb-4"> <label for="password" class="block text-[#c9d1d9] text-sm font-medium mb-2">Password</label> <input type="password" id="password" name="password" required placeholder="Enter your password" class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"> </div> <!-- Remember me --> <div class="flex items-center justify-between mb-6"> <label class="flex items-center gap-2"> <input type="checkbox" name="remember" class="w-4 h-4 rounded bg-[#0d1117] border-[#30363d] text-[#58a6ff] focus:ring-[#58a6ff] focus:ring-offset-0"> <span class="text-[#8b949e] text-sm">Remember me</span> </label> <a href="#" class="text-[#58a6ff] hover:text-[#79c0ff] text-sm no-underline focus:outline-none focus:ring-0" onclick="event.preventDefault(); alert('Password reset is not available yet. Backend API is not implemented.');">
Forgot password?
</a> </div> <!-- Submit --> <button type="submit" class="w-full bg-[#1f6feb] hover:bg-[#388bfd] text-white px-4 py-2 rounded-md text-sm font-semibold transition-colors focus:outline-none focus:ring-0">
Sign In
</button> </form> <!-- Divider --> <div class="flex items-center gap-3 my-6"> <div class="flex-1 border-t border-[#30363d]"></div> <span class="text-[#484f58] text-xs uppercase">or</span> <div class="flex-1 border-t border-[#30363d]"></div> </div> <!-- GitHub OAuth placeholder --> <button type="button" class="w-full bg-[#21262d] hover:bg-[#30363d] text-[#c9d1d9] px-4 py-2 rounded-md text-sm font-semibold transition-colors border border-[#30363d] flex items-center justify-center gap-2 focus:outline-none focus:ring-0" onclick="alert('GitHub OAuth is not available yet. Backend API is not implemented.')"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"> <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path> </svg>
Continue with GitHub
</button> <!-- Sign up link --> <p class="text-[#8b949e] text-sm text-center mt-6">
Don't have an account? <a href="/signup" class="text-[#58a6ff] hover:text-[#79c0ff] no-underline focus:outline-none focus:ring-0">Sign up</a> </p> </div> </div> </main> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html>

9
dist/signup/index.html vendored Normal file
View file

@ -0,0 +1,9 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css"></head> <body data-astro-cid-sckkx6r4> <div class="min-h-screen bg-[#0d1117]"> <!-- Header --> <header class="bg-[#161b22] border-b border-[#30363d]"> <div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between"> <a href="/" class="flex items-center gap-3 no-underline focus:outline-none focus:ring-0"> <svg class="w-8 h-8 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20"> <path d="M10 2a8 8 0 100 16 8 8 0 000-16zm1 11H9v-2h2v2zm0-4H9V5h2v4z"></path> </svg> <span class="text-xl font-semibold text-[#c9d1d9]">PR Dojo</span> </a> <nav class="flex items-center gap-4"> <a href="/" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Home</a> </nav> </div> </header> <main class="flex items-center justify-center px-4 py-16"> <div class="w-full max-w-md"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-8"> <div class="text-center mb-8"> <h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2">Create your account</h1> <p class="text-[#8b949e] text-sm">Join PR Dojo to track your progress and earn XP</p> </div> <form on:submit|preventDefault="(e) => {
e.preventDefault();
alert(&#34;Sign up is not available yet. Backend API is not implemented.&#34;);
}"> <!-- Username --> <div class="mb-4"> <label for="username" class="block text-[#c9d1d9] text-sm font-medium mb-2">Username</label> <input type="text" id="username" name="username" required placeholder="Enter your username" class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"> </div> <!-- Email --> <div class="mb-4"> <label for="email" class="block text-[#c9d1d9] text-sm font-medium mb-2">Email</label> <input type="email" id="email" name="email" required placeholder="you@example.com" class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"> </div> <!-- Password --> <div class="mb-4"> <label for="signup-password" class="block text-[#c9d1d9] text-sm font-medium mb-2">Password</label> <input type="password" id="signup-password" name="password" required minlength="8" placeholder="At least 8 characters" class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"> </div> <!-- Confirm Password --> <div class="mb-6"> <label for="confirm-password" class="block text-[#c9d1d9] text-sm font-medium mb-2">Confirm Password</label> <input type="password" id="confirm-password" name="confirm_password" required minlength="8" placeholder="Re-enter your password" class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"> </div> <!-- Submit --> <button type="submit" class="w-full bg-[#1f6feb] hover:bg-[#388bfd] text-white px-4 py-2 rounded-md text-sm font-semibold transition-colors focus:outline-none focus:ring-0">
Sign Up
</button> </form> <!-- Divider --> <div class="flex items-center gap-3 my-6"> <div class="flex-1 border-t border-[#30363d]"></div> <span class="text-[#484f58] text-xs uppercase">or</span> <div class="flex-1 border-t border-[#30363d]"></div> </div> <!-- GitHub OAuth placeholder --> <button type="button" class="w-full bg-[#21262d] hover:bg-[#30363d] text-[#c9d1d9] px-4 py-2 rounded-md text-sm font-semibold transition-colors border border-[#30363d] flex items-center justify-center gap-2 focus:outline-none focus:ring-0" onclick="alert('GitHub OAuth is not available yet. Backend API is not implemented.')"> <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"> <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path> </svg>
Continue with GitHub
</button> <!-- Sign in link --> <p class="text-[#8b949e] text-sm text-center mt-6">
Already have an account? <a href="/signin" class="text-[#58a6ff] hover:text-[#79c0ff] no-underline focus:outline-none focus:ring-0">Sign in</a> </p> </div> </div> </main> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html>

28
dist/terms/index.html vendored Normal file
View file

@ -0,0 +1,28 @@
<!DOCTYPE html><html lang="en" class="dark" data-astro-cid-sckkx6r4> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><meta name="generator" content="Astro v6.1.7"><title>PR Dojo - Code Review Practice</title><link rel="stylesheet" href="/_astro/Layout.slfrh7tA.css"></head> <body data-astro-cid-sckkx6r4> <div class="max-w-4xl mx-auto px-4 py-16"> <h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">Terms of Service</h1> <div class="space-y-6 text-[#8b949e] leading-relaxed"> <p>
Last updated: <strong class="text-[#c9d1d9]">April 28, 2026</strong> </p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Acceptance of Terms</h2> <p>
By accessing and using PR Dojo, you accept and agree to be bound by these Terms of Service. If you do not agree to these terms, do not use this service.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Description of Service</h2> <p>
PR Dojo provides an educational platform for practicing code review skills through interactive challenges. The service includes:
</p> <ul class="list-disc list-inside space-y-2 ml-4"> <li>Buggy code challenges sourced from real rejected PRs</li> <li>Hint systems to guide learning</li> <li>XP and ranking systems to track progress</li> <li>Fix submission and review capabilities</li> </ul> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">User Accounts</h2> <p>
To access certain features, you may need to create an account. You are responsible for maintaining the confidentiality of your account credentials and for all activities under your account.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Acceptable Use</h2> <p>You agree not to:</p> <ul class="list-disc list-inside space-y-2 ml-4"> <li>Use the service for any unlawful purpose</li> <li>Attempt to gain unauthorized access to any part of the service</li> <li>Interfere with or disrupt the service or servers</li> <li>Submit malicious code as fixes</li> <li>Scrape or copy challenge content for redistribution</li> <li>Impersonate another user</li> </ul> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Intellectual Property</h2> <p>
All content on PR Dojo, including challenge descriptions, code snippets, hints, and the platform design, is the intellectual property of PR Dojo or its contributors and is protected by copyright and other intellectual property laws.
</p> <p>
You may not reproduce, distribute, modify, or create derivative works from our content without explicit permission.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">User Submissions</h2> <p>
When you submit fixes or suggestions, you grant PR Dojo a non-exclusive, royalty-free license to use, display, and incorporate your submissions into the platform. You retain ownership of your submissions.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Disclaimers</h2> <p>
PR Dojo is provided "as is" without warranties of any kind, either express or implied. We do not guarantee that the challenges are free from errors or that the service will be uninterrupted.
</p> <p>
The code challenges are for educational purposes only. Any real-world code patterns shown may not reflect best practices or current standards.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Limitation of Liability</h2> <p>
In no event shall PR Dojo or its contributors be liable for any indirect, incidental, special, consequential, or punitive damages arising from your use of the service.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Termination</h2> <p>
We reserve the right to suspend or terminate accounts that violate these terms, or to discontinue the service entirely, with or without notice.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Changes to Terms</h2> <p>
We may modify these Terms at any time. Continued use of the service after changes constitutes acceptance of the new terms.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Governing Law</h2> <p>
These Terms shall be governed by and construed in accordance with the laws of your jurisdiction, without regard to conflict of law principles.
</p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Contact</h2> <p>
For questions about these Terms, contact us at:
</p> <p><a href="mailto:legal@prdojo.example.com" class="text-[#58a6ff] hover:text-[#79c0ff]">legal@prdojo.example.com</a></p> </div> </div> <footer class="border-t border-[#30363d] mt-16"> <div class="max-w-6xl mx-auto px-4 py-12"> <div class="grid grid-cols-2 md:grid-cols-4 gap-8"> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3> <ul class="space-y-2"> <li> <a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a> </li> <li> <a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3> <ul class="space-y-2"> <li> <a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a> </li> <li> <a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3> <ul class="space-y-2"> <li> <a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a> </li> <li> <a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a> </li> <li> <a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a> </li> </ul> </div> <div> <h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3> <ul class="space-y-2"> <li> <a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a> </li> <li> <a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a> </li> </ul> </div> </div> <div class="border-t border-[#30363d] mt-8 pt-8 text-center"> <p class="text-[#8b949e] text-sm">&copy; 2026 PR Dojo. All rights reserved.</p> </div> </div> </footer></body></html>

View file

@ -31,16 +31,16 @@ const lines = code.split('\n');
title={`Mark line ${lineNum} as buggy`} title={`Mark line ${lineNum} as buggy`}
> >
<td class="w-16 text-right pr-4 select-none"> <td class="w-16 text-right pr-4 select-none">
<span class={`text-[#8b949e] line-number ${hasHint ? 'text-[#ff7b72] font-semibold' : ''}`}> <span class={`text-[#8b949e] line-number ${hasHint ? 'has-hint-color' : ''}`}>
{lineNum} {lineNum}
</span> </span>
</td> </td>
<td class="px-4 py-0.5 whitespace-pre"> <td class="px-4 py-0.5 whitespace-pre">
<span class={`text-[#e6edf3] ${hasHint ? 'bg-[#ff7b7233] px-2 -mx-2' : ''}`}> <span class={`text-[#e6edf3] ${hasHint ? 'has-hint-bg bg-[#ff7b7233] px-2 -mx-2' : ''}`}>
{line || ' '} {line || ' '}
</span> </span>
{hasHint && ( {hasHint && (
<span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity"> <span class="ml-2 hint text-[#79c0ff] text-xs hidden">
• {hintText?.replace(`Line ${lineNum}: `, '')} • {hintText?.replace(`Line ${lineNum}: `, '')}
</span> </span>
)} )}
@ -52,19 +52,7 @@ const lines = code.split('\n');
</table> </table>
</div> </div>
{hints.length > 0 && (
<div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]">
<h3 class="text-sm font-semibold text-[#c9d1d9] mb-2">Hints:</h3>
<ul class="space-y-1">
{hints.map((hint) => (
<li class="text-sm text-[#8b949e] flex items-start gap-2">
<span class="text-[#79c0ff] mt-0.5">•</span>
<span>{hint}</span>
</li>
))}
</ul>
</div>
)}
</div> </div>
</div> </div>
@ -87,6 +75,19 @@ const lines = code.split('\n');
tr.bugged { tr.bugged {
background-color: #f8514918; background-color: #f8514918;
} }
.diff-viewer-container:not(.revealed) tr.bugged .line-number.has-hint-color {
color: #f85149 !important;
}
.diff-viewer-container:not(.revealed) tr:not(.bugged) span.line-number.has-hint-color {
color: #8b949e !important;
font-weight: normal !important;
}
.diff-viewer-container.revealed tr.bugged .hint {
display: inline;
}
.diff-viewer-container:not(.revealed) td > span.has-hint-bg {
background-color: transparent !important;
}
</style> </style>
<script client:load> <script client:load>

View file

@ -0,0 +1,56 @@
<footer class="border-t border-[#30363d] mt-16">
<div class="max-w-6xl mx-auto px-4 py-12">
<div class="grid grid-cols-2 md:grid-cols-4 gap-8">
<div>
<h3 class="text-[#c9d1d9] font-semibold mb-4">PR Dojo</h3>
<ul class="space-y-2">
<li>
<a href="/" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Challenges</a>
</li>
<li>
<a href="/profile" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Profile</a>
</li>
</ul>
</div>
<div>
<h3 class="text-[#c9d1d9] font-semibold mb-4">Learn</h3>
<ul class="space-y-2">
<li>
<a href="/about" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">About</a>
</li>
<li>
<a href="/faq" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">FAQ</a>
</li>
</ul>
</div>
<div>
<h3 class="text-[#c9d1d9] font-semibold mb-4">Legal</h3>
<ul class="space-y-2">
<li>
<a href="/imprint" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Imprint</a>
</li>
<li>
<a href="/privacy" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Privacy Policy</a>
</li>
<li>
<a href="/terms" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Terms of Service</a>
</li>
</ul>
</div>
<div>
<h3 class="text-[#c9d1d9] font-semibold mb-4">Connect</h3>
<ul class="space-y-2">
<li>
<a href="https://github.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">GitHub</a>
</li>
<li>
<a href="https://twitter.com" target="_blank" rel="noopener noreferrer" class="text-[#8b949e] text-sm hover:text-[#c9d1d9]">Twitter</a>
</li>
</ul>
</div>
</div>
<div class="border-t border-[#30363d] mt-8 pt-8 text-center">
<p class="text-[#8b949e] text-sm">&copy; {new Date().getFullYear()} PR Dojo. All rights reserved.</p>
</div>
</div>
</footer>

View file

@ -1,5 +1,6 @@
--- ---
import '../styles/global.css'; import '../styles/global.css';
import Footer from '../components/Footer.astro';
--- ---
<!doctype html> <!doctype html>
@ -13,6 +14,7 @@ import '../styles/global.css';
</head> </head>
<body> <body>
<slot /> <slot />
<Footer />
</body> </body>
</html> </html>

65
src/pages/about.astro Normal file
View file

@ -0,0 +1,65 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<div class="max-w-4xl mx-auto px-4 py-16">
<h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">About PR Dojo</h1>
<div class="space-y-6 text-[#8b949e] leading-relaxed">
<p>
<strong class="text-[#c9d1d9]">PR Dojo</strong> is a practice platform for developers who want to sharpen their code review skills. We believe that the best way to become a better reviewer is by practicing on real-world code that didn't make it through review.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Our Mission</h2>
<p>
Every day, thousands of pull requests are rejected, revised, or merged with bugs. These rejected PRs contain valuable learning opportunities — real bugs, real edge cases, real design mistakes. PR Dojo curates these mistakes into structured challenges where you can practice identifying issues and writing fixes.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">How It Works</h2>
<div class="grid gap-4 mt-4">
<div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6">
<h3 class="text-[#58a6ff] font-semibold mb-2">1. Pick a Challenge</h3>
<p class="text-sm">Browse our collection of buggy code snippets sourced from real rejected PRs. Each challenge includes a description, difficulty rating, and bug type categorization.</p>
</div>
<div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6">
<h3 class="text-[#58a6ff] font-semibold mb-2">2. Find the Bugs</h3>
<p class="text-sm">Review the code carefully, looking for logic errors, security vulnerabilities, performance issues, and style problems. Use hints if you get stuck.</p>
</div>
<div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6">
<h3 class="text-[#58a6ff] font-semibold mb-2">3. Submit Your Fix</h3>
<p class="text-sm">Write a patch that resolves the identified issues. Submit your fix and earn XP based on the difficulty and quality of your solution.</p>
</div>
</div>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Why Practice Code Review?</h2>
<p>
Strong code review skills are one of the highest-leverage abilities a developer can have. Reviewing code helps you:
</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li>Catch bugs before they reach production</li>
<li>Learn new patterns and techniques from others' code</li>
<li>Develop a critical eye for edge cases and security issues</li>
<li>Improve your own writing by understanding what to avoid</li>
<li>Communicate feedback constructively to teammates</li>
</ul>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Bug Types We Cover</h2>
<p>Our challenges span a wide range of common bug categories:</p>
<div class="flex flex-wrap gap-2 mt-4">
<span class="bg-[#1f6feb]/20 text-[#58a6ff] border border-[#1f6feb]/40 rounded-full px-3 py-1 text-sm">Logic Errors</span>
<span class="bg-[#f85149]/20 text-[#f85149] border border-[#f85149]/40 rounded-full px-3 py-1 text-sm">Security Vulnerabilities</span>
<span class="bg-[#a371f7]/20 text-[#a371f7] border border-[#a371f7]/40 rounded-full px-3 py-1 text-sm">Performance Issues</span>
<span class="bg-[#f2cc60]/20 text-[#f2cc60] border border-[#f2cc60]/40 rounded-full px-3 py-1 text-sm">Edge Cases</span>
<span class="bg-[#388bfd]/20 text-[#388bfd] border border-[#388bfd]/40 rounded-full px-3 py-1 text-sm">Type Errors</span>
<span class="bg-[#a5d6ff]/20 text-[#a5d6ff] border border-[#a5d6ff]/40 rounded-full px-3 py-1 text-sm">API Misuse</span>
<span class="bg-[#8b949e]/20 text-[#8b949e] border border-[#8b949e]/40 rounded-full px-3 py-1 text-sm">Resource Leaks</span>
</div>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Get Involved</h2>
<p>
PR Dojo is an open project. If you have rejected PRs you'd like to contribute as challenges, or if you want to help build the platform, check out our GitHub repository and open an issue or pull request.
</p>
</div>
</div>
</Layout>

View file

@ -266,10 +266,16 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
return; return;
} }
// Reveal bugged lines and hints in the diff viewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.add('revealed');
}
// Show success message // Show success message
if (submissionMessage) { if (submissionMessage) {
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)'; submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
submissionMessage.className = 'mt-3 text-sm text-[3ac840]'; submissionMessage.className = 'mt-3 text-sm text-[#3ac840]';
submissionMessage.classList.remove('hidden'); submissionMessage.classList.remove('hidden');
} }
}); });
@ -285,7 +291,11 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
if (submissionMessage) submissionMessage.classList.add('hidden'); if (submissionMessage) submissionMessage.classList.add('hidden');
updateUI(); updateUI();
// Reset bugged state in DiffViewer // Reset bugged state and revealed state in DiffViewer
const dvContainer = document.querySelector('.diff-viewer-container');
if (dvContainer) {
dvContainer.classList.remove('revealed');
}
document.querySelectorAll('tr.bugged').forEach(row => { document.querySelectorAll('tr.bugged').forEach(row => {
row.classList.remove('bugged'); row.classList.remove('bugged');
}); });

61
src/pages/faq.astro Normal file
View file

@ -0,0 +1,61 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<div class="max-w-4xl mx-auto px-4 py-16">
<h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">Frequently Asked Questions</h1>
<div class="space-y-8">
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">What is PR Dojo?</h2>
<p class="text-[#8b949e] leading-relaxed">PR Dojo is a practice platform for developers to improve their code review skills. We present you with buggy code from real rejected pull requests, and challenge you to find the issues and write fixes.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">Is PR Dojo free to use?</h2>
<p class="text-[#8b949e] leading-relaxed">Yes, PR Dojo is completely free. All challenges are available without any cost or subscription. We believe in making code review practice accessible to everyone.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">How do I start?</h2>
<p class="text-[#8b949e] leading-relaxed">Head to the <a href="/" class="text-[#58a6ff] hover:text-[#79c0ff]">Challenges</a> page and pick a challenge that matches your skill level. Each challenge shows its difficulty rating, XP value, and the type of bug you'll be looking for.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">How are challenges scored?</h2>
<p class="text-[#8b949e] leading-relaxed">Each challenge awards XP based on its difficulty (1-5 stars). You earn the full amount when your submitted fix correctly resolves all identified issues. Partial credit may be awarded for fixing some but not all bugs.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">What if I get stuck?</h2>
<p class="text-[#8b949e] leading-relaxed">Every challenge provides hints that reveal line-by-line descriptions of the issues. Hints are designed to guide you without giving away the full solution — use them when you need a nudge in the right direction.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">Can I submit my own challenges?</h2>
<p class="text-[#8b949e] leading-relaxed">Not yet, but it's on our roadmap. We're planning to add a contribution system where experienced developers can submit their own rejected PRs as practice challenges. Stay tuned!</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">What programming languages are supported?</h2>
<p class="text-[#8b949e] leading-relaxed">Currently we focus on TypeScript and JavaScript, as these are the most common languages in the PRs we curate. We plan to expand to other languages like Python, Go, and Rust as the platform grows.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">How does the XP system work?</h2>
<p class="text-[#8b949e] leading-relaxed">XP (experience points) are awarded when you successfully submit a fix for a challenge. Higher difficulty challenges award more XP. Your XP total contributes to your rank and is displayed on your profile.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">Can I use PR Dojo for team training?</h2>
<p class="text-[#8b949e] leading-relaxed">Absolutely. Many teams use PR Dojo to practice code review as a group activity. While individual profiles are currently supported, we're planning team features like shared leaderboards and team challenges.</p>
</div>
<div>
<h2 class="text-xl font-semibold text-[#c9d1d9] mb-3">Where do the challenges come from?</h2>
<p class="text-[#8b949e] leading-relaxed">Our challenges are sourced from real rejected pull requests across open-source projects (with permission and anonymization). We strip away sensitive information and focus purely on the code issues to create safe learning environments.</p>
</div>
</div>
</div>
</Layout>

42
src/pages/imprint.astro Normal file
View file

@ -0,0 +1,42 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<div class="max-w-4xl mx-auto px-4 py-16">
<h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">Imprint</h1>
<div class="space-y-6 text-[#8b949e] leading-relaxed">
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Information according to § 5 TMG</h2>
<div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6 space-y-3">
<p><strong class="text-[#c9d1d9]">PR Dojo</strong></p>
<p>Project Lead: [Your Name]</p>
<p>Address: [Your Address]</p>
<p>[Postal Code] [City]</p>
<p>[Country]</p>
</div>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Contact</h2>
<div class="bg-[#161b22] border border-[#30363d] rounded-lg p-6 space-y-2">
<p>Email: [contact@prdojo.example.com](mailto:contact@prdojo.example.com)</p>
<p>GitHub: [github.com/prdojo](https://github.com/prdojo)</p>
</div>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Disclaimer</h2>
<p>
The content on this site is provided with the utmost care. However, we do not guarantee the accuracy, completeness, or timeliness of any content on this website.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Links and References</h2>
<p>
This website contains links to external websites of third parties, over whose content we have no influence. Therefore, we cannot assume any liability for these external contents. The respective providers or operators of the pages are always responsible for the content of the linked pages.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-8 mb-4">Copyright</h2>
<p>
The content and works created by us on these pages are subject to German and international copyright laws. Any duplication, processing, distribution, or any form of utilization beyond the scope of copyright law shall require the prior written consent of its respective author or creator. Downloads and copies of this site are only permitted for private, non-commercial use.
</p>
</div>
</div>
</Layout>

View file

@ -29,9 +29,7 @@ const stars = (difficulty: number) => '★'.repeat(difficulty) + '☆'.repeat(5
<nav class="flex items-center gap-4"> <nav class="flex items-center gap-4">
<a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">Challenges</a> <a href="/" class="text-[#58a6ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">Challenges</a>
<a href="/profile" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Profile</a> <a href="/profile" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Profile</a>
<button class="bg-[#1f6feb] hover:bg-[#388bfd] text-white px-4 py-1.5 rounded-md text-sm font-semibold transition-colors focus:outline-none focus:ring-0"> <a href="/signin" class="text-[#58a6ff] hover:text-[#79c0ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">Sign In</a>
Sign In
</button>
</nav> </nav>
</div> </div>
</header> </header>
@ -53,24 +51,6 @@ const stars = (difficulty: number) => '★'.repeat(difficulty) + '☆'.repeat(5
</div> </div>
</div> </div>
<!-- Stats Banner -->
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-8">
<div class="grid grid-cols-3 gap-6 text-center">
<div>
<div class="text-3xl font-bold text-[#58a6ff] mb-1">0</div>
<div class="text-sm text-[#8b949e]">XP Earned</div>
</div>
<div>
<div class="text-3xl font-bold text-[#a5d6ff] mb-1">Novice</div>
<div class="text-sm text-[#8b949e]">Current Rank</div>
</div>
<div>
<div class="text-3xl font-bold text-[#79c0ff] mb-1">0</div>
<div class="text-sm text-[#8b949e]">Challenges Solved</div>
</div>
</div>
</div>
<!-- Challenges Section --> <!-- Challenges Section -->
<div class="mb-8"> <div class="mb-8">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
@ -144,11 +124,5 @@ const stars = (difficulty: number) => '★'.repeat(difficulty) + '☆'.repeat(5
</div> </div>
</main> </main>
<!-- Footer --> </div>
<footer class="border-t border-[#30363d] mt-12">
<div class="max-w-6xl mx-auto px-4 py-6 text-center text-[#8b949e] text-sm">
<p>Code Review Hunter - Practice fixing rejected PRs and earn XP</p>
</div>
</footer>
</div>
</Layout> </Layout>

85
src/pages/privacy.astro Normal file
View file

@ -0,0 +1,85 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<div class="max-w-4xl mx-auto px-4 py-16">
<h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">Privacy Policy</h1>
<div class="space-y-6 text-[#8b949e] leading-relaxed">
<p>
Last updated: <strong class="text-[#c9d1d9]">{new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</strong>
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Introduction</h2>
<p>
PR Dojo ("we", "our", or "us") is committed to protecting your privacy. This Privacy Policy explains how your personal information is collected, used, and disclosed by PR Dojo.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Information We Collect</h2>
<p>
PR Dojo is currently in its early stages and does not collect personal data beyond what is necessary for functionality. As we grow, we may collect:
</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li><strong class="text-[#c9d1d9]">Account Data:</strong> When you create a profile, we may collect a username and email address.</li>
<li><strong class="text-[#c9d1d9]">Usage Data:</strong> We may log which challenges you've completed and your XP score to track progress.</li>
<li><strong class="text-[#c9d1d9]">Technical Data:</strong> Standard server logs may record IP addresses, browser type, and access times for security and analytics purposes.</li>
</ul>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">How We Use Your Information</h2>
<p>We use collected information to:</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li>Provide and improve our services</li>
<li>Track your progress and XP scores</li>
<li>Communicate with you about updates or changes</li>
<li>Ensure the security of our platform</li>
</ul>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Data Sharing</h2>
<p>
We do not sell, trade, or rent your personal information to third parties. We may share data only in the following circumstances:
</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li>With your explicit consent</li>
<li>To comply with legal obligations</li>
<li>With service providers who assist in operating our platform (under confidentiality agreements)</li>
</ul>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Data Security</h2>
<p>
We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Your Rights</h2>
<p>Depending on your jurisdiction, you may have the right to:</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li>Access your personal data</li>
<li>Request correction of inaccurate data</li>
<li>Request deletion of your data</li>
<li>Object to or restrict processing of your data</li>
<li>Data portability</li>
</ul>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Cookies</h2>
<p>
PR Dojo may use cookies and similar technologies for functionality, analytics, and personalization. You can control cookie settings through your browser preferences.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Children's Privacy</h2>
<p>
PR Dojo is not intended for children under 13. We do not knowingly collect personal information from children under 13.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Changes to This Policy</h2>
<p>
We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new policy on this page and updating the "last updated" date.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Contact Us</h2>
<p>
If you have questions about this Privacy Policy, please contact us at:
</p>
<p><a href="mailto:privacy@prdojo.example.com" class="text-[#58a6ff] hover:text-[#79c0ff]">privacy@prdojo.example.com</a></p>
</div>
</div>
</Layout>

View file

@ -16,13 +16,13 @@ import Layout from '../layouts/Layout.astro';
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6">
<div class="flex items-center gap-6"> <div class="flex items-center gap-6">
<div class="w-20 h-20 bg-[#21262d] rounded-full flex items-center justify-center"> <div class="w-20 h-20 bg-[#21262d] rounded-full flex items-center justify-center">
<svg class="w-12 h-12 text-[#8b949e]" fill="currentColor" viewBox="0 0 20 20"> <svg id="avatar-placeholder" class="w-12 h-12 text-[#8b949e]" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>
</svg> </svg>
</div> </div>
<div> <div>
<h1 class="text-2xl font-semibold text-[#c9d1d9]">Anonymous User</h1> <h1 id="profile-name" class="text-2xl font-semibold text-[#c9d1d9]">Loading...</h1>
<p class="text-[#8b949e] text-sm mt-1">Code Review Practitioner</p> <p id="profile-title" class="text-[#8b949e] text-sm mt-1">Code Review Practitioner</p>
</div> </div>
</div> </div>
</div> </div>
@ -36,7 +36,7 @@ import Layout from '../layouts/Layout.astro';
</svg> </svg>
<span class="text-[#8b949e] text-sm">Total XP</span> <span class="text-[#8b949e] text-sm">Total XP</span>
</div> </div>
<div class="text-3xl font-bold text-[#58a6ff]">0</div> <div id="stat-xp" class="text-3xl font-bold text-[#58a6ff]">?</div>
</div> </div>
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5">
@ -46,7 +46,7 @@ import Layout from '../layouts/Layout.astro';
</svg> </svg>
<span class="text-[#8b949e] text-sm">Challenges Solved</span> <span class="text-[#8b949e] text-sm">Challenges Solved</span>
</div> </div>
<div class="text-3xl font-bold text-[#c9d1d9]">0<span class="text-[#8b949e] text-xl">/5</span></div> <div id="stat-solved" class="text-3xl font-bold text-[#c9d1d9]">?/<span class="text-[#8b949e] text-xl">?</span></div>
</div> </div>
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-5"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-5">
@ -56,41 +56,303 @@ import Layout from '../layouts/Layout.astro';
</svg> </svg>
<span class="text-[#8b949e] text-sm">Current Streak</span> <span class="text-[#8b949e] text-sm">Current Streak</span>
</div> </div>
<div class="text-3xl font-bold text-[#79c0ff]">0<span class="text-[#8b949e] text-xl"> days</span></div> <div id="stat-streak" class="text-3xl font-bold text-[#79c0ff]">?<span class="text-[#8b949e] text-xl"> days</span></div>
</div> </div>
</div> </div>
<!-- Rank Progress --> <!-- Rank Progress -->
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<span class="text-[#c9d1d9] font-semibold">Current Rank: Novice</span> <span id="rank-label" class="text-[#c9d1d9] font-semibold">Loading rank...</span>
<span class="text-[#8b949e] text-sm">0 / 500 XP</span> <span id="rank-xp" class="text-[#8b949e] text-sm">? / ? XP</span>
</div> </div>
<div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <div class="h-3 bg-[#21262d] rounded-full overflow-hidden">
<div class="h-full bg-[#58a6ff] w-0"></div> <div id="rank-bar" class="h-full bg-[#58a6ff] w-0 transition-all duration-500"></div>
</div> </div>
<div class="mt-4 flex items-center justify-between"> </div>
<span class="text-[#c9d1d9] font-semibold">Next: Apprentice</span>
<span class="text-[#8b949e] text-sm">500 / 1500 XP</span> <!-- Badges -->
</div> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 mb-6">
<div class="h-3 bg-[#21262d] rounded-full overflow-hidden"> <h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Badges</h2>
<div class="h-full bg-[#a5d6ff] w-0"></div> <div id="badge-list" class="flex flex-wrap gap-3">
<div class="text-center text-[#8b949e] py-4">
<p>Loading badges...</p>
</div>
</div> </div>
</div> </div>
<!-- Recent Activity --> <!-- Recent Activity -->
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-6"> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6">
<h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Recent Activity</h2> <h2 class="text-lg font-semibold text-[#c9d1d9] mb-4">Recent Activity</h2>
<div class="text-center text-[#8b949e] py-8"> <div id="activity-list" class="space-y-3">
<svg class="w-12 h-12 mx-auto mb-3 text-[#30363d]" fill="currentColor" viewBox="0 0 20 20"> <div class="text-center text-[#8b949e] py-8">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/> <svg class="w-12 h-12 mx-auto mb-3 text-[#30363d]" fill="currentColor" viewBox="0 0 20 20">
</svg> <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/>
<p>No challenges completed yet</p> </svg>
<a href="/challenges/1" class="text-[#58a6ff] mt-2 inline-block no-underline"> <p>No challenges completed yet</p>
Start your first challenge → <a href="/challenges/1" class="text-[#58a6ff] mt-2 inline-block no-underline">
</a> Start your first challenge →
</a>
</div>
</div> </div>
</div> </div>
</main> </main>
</div> </div>
<script client:load>
(function() {
var API_BASE = 'http://localhost:9090/api';
// Fallback rank tiers if API doesn't provide rank info
var rankTiers = [
{ name: 'Novice', xpThreshold: 0 },
{ name: 'Apprentice', xpThreshold: 500 },
{ name: 'Skilled', xpThreshold: 1500 },
{ name: 'Expert', xpThreshold: 3000 },
{ name: 'Master', xpThreshold: 5000 },
];
function getNextTier(currentThreshold) {
for (var i = 0; i < rankTiers.length; i++) {
if (rankTiers[i].xpThreshold > currentThreshold) {
return rankTiers[i];
}
}
return null;
}
function setWidth(el, pct) {
if (!el) return;
el.style.width = Math.min(100, Math.max(0, pct)) + '%';
}
function loadProfile() {
fetch(API_BASE + '/me')
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(user) {
// Profile header
var nameEl = document.getElementById('profile-name');
var titleEl = document.getElementById('profile-title');
if (nameEl) nameEl.textContent = user.username || user.name || 'Anonymous User';
if (titleEl) titleEl.textContent = user.title || 'Code Review Practitioner';
if (user.avatarUrl) {
var avatarEl = document.getElementById('avatar-placeholder');
if (avatarEl) {
var img = document.createElement('img');
img.src = user.avatarUrl;
img.alt = user.username || 'Avatar';
img.className = 'w-20 h-20 rounded-full object-cover';
avatarEl.replaceWith(img);
}
}
// Badges
var badges = user.badges || user.badge_list || [];
var badgeList = document.getElementById('badge-list');
if (badgeList) {
if (badges.length === 0) {
badgeList.innerHTML = '<div class="text-center text-[#8b949e] py-4"><p>No badges earned yet</p></div>';
} else {
// Tier colors and icons
var tierMap = {
'Beginner': { icon: '🌱', color: '#3fb950' },
'Intermediate': { icon: '🔧', color: '#58a6ff' },
'Advanced': { icon: '⚡', color: '#d29922' },
'Expert': { icon: '🏆', color: '#f85149' },
'Hard Mode': { icon: '💀', color: '#a371f7' },
'Legendary': { icon: '👑', color: '#f778ba' },
};
var defaultTier = { icon: '🏅', color: '#8b949e' };
fetch(API_BASE + '/badges')
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(data) {
var definitions = data.definitions || data.badges || [];
var badgeMap = {};
for (var i = 0; i < definitions.length; i++) {
var def = definitions[i];
var tierInfo = def.tier || {};
var tierName = tierInfo.level || tierInfo.name || '';
var tierData = tierMap[tierName] || defaultTier;
badgeMap[def.id] = {
name: def.title || def.id,
icon: tierData.icon,
desc: def.description || '',
color: tierData.color,
};
}
badgeList.innerHTML = '';
for (var b = 0; b < badges.length; b++) {
var badgeId = badges[b];
var meta = badgeMap[badgeId] || { name: badgeId, icon: '🏅', desc: 'A badge earned in challenges', color: '#8b949e' };
var badgeEl = document.createElement('div');
badgeEl.className = 'flex items-center gap-3 bg-[#21262d] border border-[#30363d] rounded-md px-4 py-3 min-w-[200px]';
badgeEl.title = meta.desc;
badgeEl.innerHTML = '<div class="w-10 h-10 rounded-full flex items-center justify-center text-xl" style="background-color:' + meta.color + '20">' + meta.icon + '</div><div><p class="text-[#c9d1d9] text-sm font-medium">' + meta.name + '</p><p class="text-[#8b949e] text-xs">' + meta.desc + '</p></div>';
badgeList.appendChild(badgeEl);
}
})
.catch(function(err) {
console.error('Failed to load badge definitions:', err);
var fallbackMeta = {
'first-bug': { name: 'First Bug', icon: '🐛', desc: 'Found your first bug', color: '#3fb950' },
'rapid-solver': { name: 'Rapid Solver', icon: '⚡', desc: 'Solved 10 challenges quickly', color: '#d29922' },
'streak-master': { name: 'Streak Master', icon: '🔥', desc: '7-day streak achieved', color: '#f85149' },
'xp-hunter': { name: 'XP Hunter', icon: '⭐', desc: 'Accumulated 1000 XP', color: '#58a6ff' },
'bug-slayer': { name: 'Bug Slayer', icon: '⚔️', desc: 'Solved 50 challenges', color: '#a371f7' },
'perfect-patch': { name: 'Perfect Patch', icon: '🎯', desc: 'Submitted a perfect patch', color: '#f778ba' },
'code-reviewer': { name: 'Code Reviewer', icon: '📋', desc: 'Completed first code review', color: '#79c0ff' },
'champion': { name: 'Champion', icon: '🏆', desc: 'Reached Master rank', color: '#d29922' },
};
badgeList.innerHTML = '';
for (var b = 0; b < badges.length; b++) {
var badgeId = badges[b];
var meta = fallbackMeta[badgeId] || { name: badgeId, icon: '🏅', desc: 'A badge earned in challenges', color: '#8b949e' };
var badgeEl = document.createElement('div');
badgeEl.className = 'flex items-center gap-3 bg-[#21262d] border border-[#30363d] rounded-md px-4 py-3 min-w-[200px]';
badgeEl.title = meta.desc;
badgeEl.innerHTML = '<div class="w-10 h-10 rounded-full flex items-center justify-center text-xl" style="background-color:' + meta.color + '20">' + meta.icon + '</div><div><p class="text-[#c9d1d9] text-sm font-medium">' + meta.name + '</p><p class="text-[#8b949e] text-xs">' + meta.desc + '</p></div>';
badgeList.appendChild(badgeEl);
}
});
}
}
// Stats - try snake_case first (API format), then camelCase fallbacks
var xp = user.total_xp != null ? user.total_xp : (user.xp != null ? user.xp : (user.totalXP != null ? user.totalXP : 0));
var solved = user.solved_count != null ? user.solved_count : (user.challengesSolved != null ? user.challengesSolved : (user.solvedCount != null ? user.solvedCount : 0));
var streak = user.streak != null ? user.streak : (user.current_streak != null ? user.current_streak : (user.currentStreak != null ? user.currentStreak : 0));
// Fetch total challenges dynamically from API
var totalChallenges = user.total_challenges != null ? user.total_challenges : (user.totalChallenges != null ? user.totalChallenges : null);
if (totalChallenges == null) {
fetch(API_BASE + '/challenges/totalamount')
.then(function(res) {
if (!res.ok) throw new Error('HTTP ' + res.status);
return res.json();
})
.then(function(data) {
var total = typeof data === 'number' ? data : (data.total_amount != null ? data.total_amount : (data.totalAmount != null ? data.totalAmount : (data.total != null ? data.total : '?')));
var solvedEl = document.getElementById('stat-solved');
if (solvedEl) solvedEl.innerHTML = solved + '<span class="text-[#8b949e] text-xl">/' + total + '</span>';
})
.catch(function(err) {
console.error('Failed to load total challenges:', err);
var solvedEl = document.getElementById('stat-solved');
if (solvedEl) solvedEl.innerHTML = solved + '<span class="text-[#8b949e] text-xl">/?</span>';
});
} else {
// totalChallenges already set from /me response
}
var xpEl = document.getElementById('stat-xp');
if (xpEl) xpEl.textContent = String(xp);
var solvedEl = document.getElementById('stat-solved');
if (solvedEl) solvedEl.innerHTML = solved + '<span class="text-[#8b949e] text-xl">/' + totalChallenges + '</span>';
var streakEl = document.getElementById('stat-streak');
if (streakEl) streakEl.innerHTML = streak + '<span class="text-[#8b949e] text-xl"> days</span>';
// Rank - use API rank object if available, otherwise compute from tiers
var rankLabelEl = document.getElementById('rank-label');
var rankXpEl = document.getElementById('rank-xp');
var rankBar = document.getElementById('rank-bar');
if (user.rank && user.rank.title) {
// API provides rank object
var apiRank = user.rank;
var rankTitle = apiRank.title;
var currentXp = apiRank.current_xp != null ? apiRank.current_xp : xp;
var currentThreshold = apiRank.xp_threshold != null ? apiRank.xp_threshold : 0;
// Find next tier from fallback list
var nextTier = getNextTier(currentThreshold);
if (nextTier) {
var range = nextTier.xpThreshold - currentThreshold;
var pct = range > 0 ? ((currentXp - currentThreshold) / range) * 100 : 100;
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + rankTitle + ' \u2192 Next: ' + nextTier.name;
if (rankXpEl) rankXpEl.textContent = currentXp + ' / ' + nextTier.xpThreshold + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, pct);
}
} else {
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + rankTitle;
if (rankXpEl) rankXpEl.textContent = currentXp + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, 100);
}
}
} else {
// No rank from API, compute from tiers
var currentTier = rankTiers[0];
var nextTier = rankTiers[1];
for (var i = rankTiers.length - 1; i >= 0; i--) {
if (xp >= rankTiers[i].xpThreshold) {
currentTier = rankTiers[i];
nextTier = rankTiers[i + 1] || null;
break;
}
}
if (nextTier) {
var range = nextTier.xpThreshold - currentTier.xpThreshold;
var pct = range > 0 ? ((xp - currentTier.xpThreshold) / range) * 100 : 100;
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + currentTier.name + ' \u2192 Next: ' + nextTier.name;
if (rankXpEl) rankXpEl.textContent = xp + ' / ' + nextTier.xpThreshold + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, pct);
}
} else {
if (rankLabelEl) rankLabelEl.textContent = 'Current Rank: ' + currentTier.name;
if (rankXpEl) rankXpEl.textContent = xp + ' XP';
if (rankBar) {
rankBar.className = 'h-full bg-[#58a6ff] transition-all duration-500';
setWidth(rankBar, 100);
}
}
}
// Recent Activity
var activities = user.recentActivity || user.activity || [];
var activityList = document.getElementById('activity-list');
if (activityList && activities.length > 0) {
activityList.innerHTML = '';
for (var a = 0; a < activities.length && a < 10; a++) {
var activity = activities[a];
var item = document.createElement('div');
item.className = 'flex items-center gap-3 p-3 bg-[#21262d] rounded-md';
var isSolved = activity.type === 'solved' || activity.type === 'completed';
var checkSvg = '<svg class="w-5 h-5 text-[#3fb950] flex-shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>';
var infoSvg = '<svg class="w-5 h-5 text-[#58a6ff] flex-shrink-0" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd"/></svg>';
var xpHtml = activity.xpGained ? '<span class="text-[#58a6ff] text-sm font-medium">+' + activity.xpGained + ' XP</span>' : '';
item.innerHTML = (isSolved ? checkSvg : infoSvg) + '<div class="flex-1 min-w-0"><p class="text-[#c9d1d9] text-sm truncate">' + (activity.title || activity.description || 'Challenge completed') + '</p><p class="text-[#8b949e] text-xs">' + (activity.date || activity.timestamp || '') + '</p></div>' + xpHtml;
activityList.appendChild(item);
}
}
})
.catch(function(err) {
console.error('Failed to load profile:', err);
var nameEl = document.getElementById('profile-name');
if (nameEl) nameEl.textContent = 'Anonymous User';
var activityList = document.getElementById('activity-list');
if (activityList) {
activityList.innerHTML = '<div class="text-center text-[#f85149] py-4">Failed to load profile data. Is the API running?</div>';
}
});
}
loadProfile();
})();
</script>
</Layout> </Layout>

108
src/pages/signin.astro Normal file
View file

@ -0,0 +1,108 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<div class="min-h-screen bg-[#0d1117]">
<!-- Header -->
<header class="bg-[#161b22] border-b border-[#30363d]">
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
<a href="/" class="flex items-center gap-3 no-underline focus:outline-none focus:ring-0">
<svg class="w-8 h-8 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2a8 8 0 100 16 8 8 0 000-16zm1 11H9v-2h2v2zm0-4H9V5h2v4z"/>
</svg>
<span class="text-xl font-semibold text-[#c9d1d9]">PR Dojo</span>
</a>
<nav class="flex items-center gap-4">
<a href="/" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Home</a>
</nav>
</div>
</header>
<main class="flex items-center justify-center px-4 py-16">
<div class="w-full max-w-md">
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-8">
<div class="text-center mb-8">
<h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2">Sign in to PR Dojo</h1>
<p class="text-[#8b949e] text-sm">Continue your code review practice</p>
</div>
<form on:submit|preventDefault={(e) => { e.preventDefault(); alert('Sign in is not available yet. Backend API is not implemented.'); }}>
<!-- Email -->
<div class="mb-4">
<label for="email" class="block text-[#c9d1d9] text-sm font-medium mb-2">Email</label>
<input
type="email"
id="email"
name="email"
required
placeholder="you@example.com"
class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"
/>
</div>
<!-- Password -->
<div class="mb-4">
<label for="password" class="block text-[#c9d1d9] text-sm font-medium mb-2">Password</label>
<input
type="password"
id="password"
name="password"
required
placeholder="Enter your password"
class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"
/>
</div>
<!-- Remember me -->
<div class="flex items-center justify-between mb-6">
<label class="flex items-center gap-2">
<input
type="checkbox"
name="remember"
class="w-4 h-4 rounded bg-[#0d1117] border-[#30363d] text-[#58a6ff] focus:ring-[#58a6ff] focus:ring-offset-0"
/>
<span class="text-[#8b949e] text-sm">Remember me</span>
</label>
<a href="#" class="text-[#58a6ff] hover:text-[#79c0ff] text-sm no-underline focus:outline-none focus:ring-0" onclick="event.preventDefault(); alert('Password reset is not available yet. Backend API is not implemented.');">
Forgot password?
</a>
</div>
<!-- Submit -->
<button
type="submit"
class="w-full bg-[#1f6feb] hover:bg-[#388bfd] text-white px-4 py-2 rounded-md text-sm font-semibold transition-colors focus:outline-none focus:ring-0"
>
Sign In
</button>
</form>
<!-- Divider -->
<div class="flex items-center gap-3 my-6">
<div class="flex-1 border-t border-[#30363d]"></div>
<span class="text-[#484f58] text-xs uppercase">or</span>
<div class="flex-1 border-t border-[#30363d]"></div>
</div>
<!-- GitHub OAuth placeholder -->
<button
type="button"
class="w-full bg-[#21262d] hover:bg-[#30363d] text-[#c9d1d9] px-4 py-2 rounded-md text-sm font-semibold transition-colors border border-[#30363d] flex items-center justify-center gap-2 focus:outline-none focus:ring-0"
onclick="alert('GitHub OAuth is not available yet. Backend API is not implemented.')"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
Continue with GitHub
</button>
<!-- Sign up link -->
<p class="text-[#8b949e] text-sm text-center mt-6">
Don't have an account? <a href="/signup" class="text-[#58a6ff] hover:text-[#79c0ff] no-underline focus:outline-none focus:ring-0">Sign up</a>
</p>
</div>
</div>
</main>
</div>
</Layout>

121
src/pages/signup.astro Normal file
View file

@ -0,0 +1,121 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<div class="min-h-screen bg-[#0d1117]">
<!-- Header -->
<header class="bg-[#161b22] border-b border-[#30363d]">
<div class="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between">
<a href="/" class="flex items-center gap-3 no-underline focus:outline-none focus:ring-0">
<svg class="w-8 h-8 text-[#58a6ff]" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 2a8 8 0 100 16 8 8 0 000-16zm1 11H9v-2h2v2zm0-4H9V5h2v4z"/>
</svg>
<span class="text-xl font-semibold text-[#c9d1d9]">PR Dojo</span>
</a>
<nav class="flex items-center gap-4">
<a href="/" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Home</a>
</nav>
</div>
</header>
<main class="flex items-center justify-center px-4 py-16">
<div class="w-full max-w-md">
<div class="bg-[#161b22] border border-[#30363d] rounded-md p-8">
<div class="text-center mb-8">
<h1 class="text-2xl font-semibold text-[#c9d1d9] mb-2">Create your account</h1>
<p class="text-[#8b949e] text-sm">Join PR Dojo to track your progress and earn XP</p>
</div>
<form on:submit|preventDefault={(e) => { e.preventDefault(); alert('Sign up is not available yet. Backend API is not implemented.'); }}>
<!-- Username -->
<div class="mb-4">
<label for="username" class="block text-[#c9d1d9] text-sm font-medium mb-2">Username</label>
<input
type="text"
id="username"
name="username"
required
placeholder="Enter your username"
class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"
/>
</div>
<!-- Email -->
<div class="mb-4">
<label for="email" class="block text-[#c9d1d9] text-sm font-medium mb-2">Email</label>
<input
type="email"
id="email"
name="email"
required
placeholder="you@example.com"
class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"
/>
</div>
<!-- Password -->
<div class="mb-4">
<label for="signup-password" class="block text-[#c9d1d9] text-sm font-medium mb-2">Password</label>
<input
type="password"
id="signup-password"
name="password"
required
minlength="8"
placeholder="At least 8 characters"
class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"
/>
</div>
<!-- Confirm Password -->
<div class="mb-6">
<label for="confirm-password" class="block text-[#c9d1d9] text-sm font-medium mb-2">Confirm Password</label>
<input
type="password"
id="confirm-password"
name="confirm_password"
required
minlength="8"
placeholder="Re-enter your password"
class="w-full bg-[#0d1117] border border-[#30363d] rounded-md px-3 py-2 text-[#c9d1d9] placeholder-[#484f58] focus:outline-none focus:border-[#58a6ff] focus:ring-1 focus:ring-[#58a6ff] transition-colors"
/>
</div>
<!-- Submit -->
<button
type="submit"
class="w-full bg-[#1f6feb] hover:bg-[#388bfd] text-white px-4 py-2 rounded-md text-sm font-semibold transition-colors focus:outline-none focus:ring-0"
>
Sign Up
</button>
</form>
<!-- Divider -->
<div class="flex items-center gap-3 my-6">
<div class="flex-1 border-t border-[#30363d]"></div>
<span class="text-[#484f58] text-xs uppercase">or</span>
<div class="flex-1 border-t border-[#30363d]"></div>
</div>
<!-- GitHub OAuth placeholder -->
<button
type="button"
class="w-full bg-[#21262d] hover:bg-[#30363d] text-[#c9d1d9] px-4 py-2 rounded-md text-sm font-semibold transition-colors border border-[#30363d] flex items-center justify-center gap-2 focus:outline-none focus:ring-0"
onclick="alert('GitHub OAuth is not available yet. Backend API is not implemented.')"
>
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
Continue with GitHub
</button>
<!-- Sign in link -->
<p class="text-[#8b949e] text-sm text-center mt-6">
Already have an account? <a href="/signin" class="text-[#58a6ff] hover:text-[#79c0ff] no-underline focus:outline-none focus:ring-0">Sign in</a>
</p>
</div>
</div>
</main>
</div>
</Layout>

94
src/pages/terms.astro Normal file
View file

@ -0,0 +1,94 @@
---
import Layout from '../layouts/Layout.astro';
---
<Layout>
<div class="max-w-4xl mx-auto px-4 py-16">
<h1 class="text-4xl font-bold text-[#c9d1d9] mb-8">Terms of Service</h1>
<div class="space-y-6 text-[#8b949e] leading-relaxed">
<p>
Last updated: <strong class="text-[#c9d1d9]">{new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</strong>
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Acceptance of Terms</h2>
<p>
By accessing and using PR Dojo, you accept and agree to be bound by these Terms of Service. If you do not agree to these terms, do not use this service.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Description of Service</h2>
<p>
PR Dojo provides an educational platform for practicing code review skills through interactive challenges. The service includes:
</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li>Buggy code challenges sourced from real rejected PRs</li>
<li>Hint systems to guide learning</li>
<li>XP and ranking systems to track progress</li>
<li>Fix submission and review capabilities</li>
</ul>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">User Accounts</h2>
<p>
To access certain features, you may need to create an account. You are responsible for maintaining the confidentiality of your account credentials and for all activities under your account.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Acceptable Use</h2>
<p>You agree not to:</p>
<ul class="list-disc list-inside space-y-2 ml-4">
<li>Use the service for any unlawful purpose</li>
<li>Attempt to gain unauthorized access to any part of the service</li>
<li>Interfere with or disrupt the service or servers</li>
<li>Submit malicious code as fixes</li>
<li>Scrape or copy challenge content for redistribution</li>
<li>Impersonate another user</li>
</ul>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Intellectual Property</h2>
<p>
All content on PR Dojo, including challenge descriptions, code snippets, hints, and the platform design, is the intellectual property of PR Dojo or its contributors and is protected by copyright and other intellectual property laws.
</p>
<p>
You may not reproduce, distribute, modify, or create derivative works from our content without explicit permission.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">User Submissions</h2>
<p>
When you submit fixes or suggestions, you grant PR Dojo a non-exclusive, royalty-free license to use, display, and incorporate your submissions into the platform. You retain ownership of your submissions.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Disclaimers</h2>
<p>
PR Dojo is provided "as is" without warranties of any kind, either express or implied. We do not guarantee that the challenges are free from errors or that the service will be uninterrupted.
</p>
<p>
The code challenges are for educational purposes only. Any real-world code patterns shown may not reflect best practices or current standards.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Limitation of Liability</h2>
<p>
In no event shall PR Dojo or its contributors be liable for any indirect, incidental, special, consequential, or punitive damages arising from your use of the service.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Termination</h2>
<p>
We reserve the right to suspend or terminate accounts that violate these terms, or to discontinue the service entirely, with or without notice.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Changes to Terms</h2>
<p>
We may modify these Terms at any time. Continued use of the service after changes constitutes acceptance of the new terms.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Governing Law</h2>
<p>
These Terms shall be governed by and construed in accordance with the laws of your jurisdiction, without regard to conflict of law principles.
</p>
<h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Contact</h2>
<p>
For questions about these Terms, contact us at:
</p>
<p><a href="mailto:legal@prdojo.example.com" class="text-[#58a6ff] hover:text-[#79c0ff]">legal@prdojo.example.com</a></p>
</div>
</div>
</Layout>