Add proper diff viewer with syntax highlighting
This commit is contained in:
parent
d9c27b4353
commit
60eaae5371
23 changed files with 2358 additions and 870 deletions
|
|
@ -26,9 +26,9 @@ src/
|
||||||
pages/
|
pages/
|
||||||
index.astro # challenge listing + hero
|
index.astro # challenge listing + hero
|
||||||
profile.astro # user profile / XP dashboard
|
profile.astro # user profile / XP dashboard
|
||||||
challenges/[slug].astro # single challenge view with DiffViewer
|
challenges/[slug].astro # single challenge view with side-by-side DiffViewer + submit fix panel
|
||||||
components/
|
components/
|
||||||
DiffViewer.astro # side-by-side code diff UI
|
DiffViewer.astro # side-by-side diff: left=read-only buggy code, right=editable fix with real-time diff highlighting
|
||||||
Welcome.astro # legacy starter component
|
Welcome.astro # legacy starter component
|
||||||
layouts/Layout.astro # root layout wrapping header/nav/footer
|
layouts/Layout.astro # root layout wrapping header/nav/footer
|
||||||
styles/global.css # global styles
|
styles/global.css # global styles
|
||||||
|
|
|
||||||
1
bun.lock
1
bun.lock
|
|
@ -5,6 +5,7 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "pr-dojo",
|
"name": "pr-dojo",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/prism": "^4.0.1",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"astro": "^6.1.7",
|
"astro": "^6.1.7",
|
||||||
"tailwindcss": "^4.2.2",
|
"tailwindcss": "^4.2.2",
|
||||||
|
|
|
||||||
1
dist/_astro/Layout.CLiT21to.css
vendored
1
dist/_astro/Layout.CLiT21to.css
vendored
File diff suppressed because one or more lines are too long
1
dist/_astro/Layout.CjyFoliM.css
vendored
Normal file
1
dist/_astro/Layout.CjyFoliM.css
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/about/index.html
vendored
2
dist/about/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
<!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.CLiT21to.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.
|
<!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.CjyFoliM.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>
|
</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.
|
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>
|
</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>
|
||||||
|
|
|
||||||
492
dist/challenges/1/index.html
vendored
492
dist/challenges/1/index.html
vendored
File diff suppressed because one or more lines are too long
471
dist/challenges/2/index.html
vendored
471
dist/challenges/2/index.html
vendored
File diff suppressed because one or more lines are too long
486
dist/challenges/3/index.html
vendored
486
dist/challenges/3/index.html
vendored
File diff suppressed because one or more lines are too long
473
dist/challenges/4/index.html
vendored
473
dist/challenges/4/index.html
vendored
File diff suppressed because one or more lines are too long
471
dist/challenges/5/index.html
vendored
471
dist/challenges/5/index.html
vendored
File diff suppressed because one or more lines are too long
2
dist/faq/index.html
vendored
2
dist/faq/index.html
vendored
File diff suppressed because one or more lines are too long
2
dist/imprint/index.html
vendored
2
dist/imprint/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
<!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.CLiT21to.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>
|
<!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.CjyFoliM.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.
|
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>
|
</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.
|
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.
|
||||||
|
|
|
||||||
2
dist/index.html
vendored
2
dist/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
<!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.CLiT21to.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 flex items-center justify-between"> <div class="flex items-center gap-3"> <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> </div> <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="/profile" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Profile</a> <a href="/signin" class="text-[#58a6ff] hover:text-[#79c0ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">Sign In</a> </nav> </div> </header> <main class="max-w-6xl mx-auto px-4 py-8"> <!-- Hero Section --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-8 mb-8"> <h1 class="text-3xl font-bold text-[#c9d1d9] mb-3">Code Review Hunter</h1> <p class="text-[#8b949e] mb-6 text-lg">
|
<!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.CjyFoliM.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 flex items-center justify-between"> <div class="flex items-center gap-3"> <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> </div> <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="/profile" class="text-[#8b949e] hover:text-[#c9d1d9] text-sm font-medium no-underline focus:outline-none focus:ring-0">Profile</a> <a href="/signin" class="text-[#58a6ff] hover:text-[#79c0ff] text-sm font-medium no-underline focus:outline-none focus:ring-0">Sign In</a> </nav> </div> </header> <main class="max-w-6xl mx-auto px-4 py-8"> <!-- Hero Section --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-8 mb-8"> <h1 class="text-3xl font-bold text-[#c9d1d9] mb-3">Code Review Hunter</h1> <p class="text-[#8b949e] mb-6 text-lg">
|
||||||
Master code review skills by fixing rejected PRs. Earn XP, climb ranks, and become a bug detection expert.
|
Master code review skills by fixing rejected PRs. Earn XP, climb ranks, and become a bug detection expert.
|
||||||
</p> <div class="flex gap-4"> <a href="/challenges/1" class="bg-[#1f6feb] hover:bg-[#388bfd] text-white px-6 py-2 rounded-md text-sm font-semibold transition-colors no-underline focus:outline-none focus:ring-0">
|
</p> <div class="flex gap-4"> <a href="/challenges/1" class="bg-[#1f6feb] hover:bg-[#388bfd] text-white px-6 py-2 rounded-md text-sm font-semibold transition-colors no-underline focus:outline-none focus:ring-0">
|
||||||
Start Challenge
|
Start Challenge
|
||||||
|
|
|
||||||
4
dist/privacy/index.html
vendored
4
dist/privacy/index.html
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
<!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.CLiT21to.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>
|
<!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.CjyFoliM.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]">May 2, 2026</strong> </p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Introduction</h2> <p>
|
Last updated: <strong class="text-[#c9d1d9]">May 3, 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.
|
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>
|
</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:
|
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:
|
||||||
|
|
|
||||||
2
dist/profile/index.html
vendored
2
dist/profile/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
<!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.CLiT21to.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> <!-- Circular Progress Bars --> <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6"> <!-- XP Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 flex flex-col items-center"> <h3 class="text-[#8b949e] text-sm font-medium mb-4">Total XP</h3> <div class="relative w-36 h-36"> <svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120"> <circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"></circle> <circle cx="60" cy="60" r="52" fill="none" stroke="#58a6ff" stroke-width="10" stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round" id="xp-ring" class="transition-all duration-700 ease-out"></circle> </svg> <div class="absolute inset-0 flex flex-col items-center justify-center"> <span id="stat-xp" class="text-2xl font-bold text-[#58a6ff]">?/?</span> <span class="text-[#8b949e] text-xs mt-1">XP</span> </div> </div> </div> <!-- Challenges Solved --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 flex flex-col items-center"> <h3 class="text-[#8b949e] text-sm font-medium mb-4">Challenges Solved</h3> <div class="relative w-36 h-36"> <svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120"> <circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"></circle> <circle cx="60" cy="60" r="52" fill="none" stroke="#a5d6ff" stroke-width="10" stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round" id="solved-ring" class="transition-all duration-700 ease-out"></circle> </svg> <div class="absolute inset-0 flex flex-col items-center justify-center"> <span id="stat-solved" class="text-2xl font-bold text-[#c9d1d9]">?/<span class="text-[#8b949e] text-lg">?</span></span> <span class="text-[#8b949e] text-xs mt-1">Solved</span> </div> </div> </div> <!-- Streak Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 flex flex-col items-center"> <h3 class="text-[#8b949e] text-sm font-medium mb-4">Current Streak</h3> <div class="relative w-36 h-36"> <svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120"> <circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"></circle> <circle cx="60" cy="60" r="52" fill="none" stroke="#79c0ff" stroke-width="10" stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round" id="streak-ring" class="transition-all duration-700 ease-out"></circle> </svg> <div class="absolute inset-0 flex flex-col items-center justify-center"> <span id="stat-streak" class="text-3xl font-bold text-[#79c0ff]">?<span class="text-[#8b949e] text-lg">d</span></span> <span class="text-[#8b949e] text-xs mt-1">days</span> </div> </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">
|
<!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.CjyFoliM.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> <!-- Circular Progress Bars --> <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6"> <!-- XP Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 flex flex-col items-center"> <h3 class="text-[#8b949e] text-sm font-medium mb-4">Total XP</h3> <div class="relative w-36 h-36"> <svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120"> <circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"></circle> <circle cx="60" cy="60" r="52" fill="none" stroke="#58a6ff" stroke-width="10" stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round" id="xp-ring" class="transition-all duration-700 ease-out"></circle> </svg> <div class="absolute inset-0 flex flex-col items-center justify-center"> <span id="stat-xp" class="text-2xl font-bold text-[#58a6ff]">?/?</span> <span class="text-[#8b949e] text-xs mt-1">XP</span> </div> </div> </div> <!-- Challenges Solved --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 flex flex-col items-center"> <h3 class="text-[#8b949e] text-sm font-medium mb-4">Challenges Solved</h3> <div class="relative w-36 h-36"> <svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120"> <circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"></circle> <circle cx="60" cy="60" r="52" fill="none" stroke="#58a6ff" stroke-width="10" stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round" id="solved-ring" class="transition-all duration-700 ease-out"></circle> </svg> <div class="absolute inset-0 flex flex-col items-center justify-center"> <span id="stat-solved" class="text-2xl font-bold text-[#c9d1d9]">?/<span class="text-[#8b949e] text-lg">?</span></span> <span class="text-[#8b949e] text-xs mt-1">Solved</span> </div> </div> </div> <!-- Streak Progress --> <div class="bg-[#161b22] border border-[#30363d] rounded-md p-6 flex flex-col items-center"> <h3 class="text-[#8b949e] text-sm font-medium mb-4">Current Streak</h3> <div class="relative w-36 h-36"> <svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120"> <circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"></circle> <circle cx="60" cy="60" r="52" fill="none" stroke="#58a6ff" stroke-width="10" stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round" id="streak-ring" class="transition-all duration-700 ease-out"></circle> </svg> <div class="absolute inset-0 flex flex-col items-center justify-center"> <span id="stat-streak" class="text-3xl font-bold text-[#79c0ff]">?<span class="text-[#8b949e] text-lg">d</span></span> <span class="text-[#8b949e] text-xs mt-1">days</span> </div> </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> </div> </main> </div> <script client:load>
|
</a> </div> </div> </div> </main> </div> <script client:load>
|
||||||
(function() {
|
(function() {
|
||||||
|
|
|
||||||
2
dist/signin/index.html
vendored
2
dist/signin/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
<!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.CLiT21to.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) => {
|
<!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.CjyFoliM.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();
|
e.preventDefault();
|
||||||
alert("Sign in is not available yet. Backend API is not implemented.");
|
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.');">
|
}"> <!-- 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.');">
|
||||||
|
|
|
||||||
2
dist/signup/index.html
vendored
2
dist/signup/index.html
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
<!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.CLiT21to.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) => {
|
<!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.CjyFoliM.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();
|
e.preventDefault();
|
||||||
alert("Sign up is not available yet. Backend API is not implemented.");
|
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">
|
}"> <!-- 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">
|
||||||
|
|
|
||||||
4
dist/terms/index.html
vendored
4
dist/terms/index.html
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
<!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.CLiT21to.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>
|
<!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.CjyFoliM.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]">May 2, 2026</strong> </p> <h2 class="text-2xl font-semibold text-[#c9d1d9] mt-10 mb-4">Acceptance of Terms</h2> <p>
|
Last updated: <strong class="text-[#c9d1d9]">May 3, 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.
|
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>
|
</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:
|
PR Dojo provides an educational platform for practicing code review skills through interactive challenges. The service includes:
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/prism": "^4.0.1",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"astro": "^6.1.7",
|
"astro": "^6.1.7",
|
||||||
"tailwindcss": "^4.2.2"
|
"tailwindcss": "^4.2.2"
|
||||||
|
|
|
||||||
|
|
@ -1,129 +1,546 @@
|
||||||
---
|
---
|
||||||
|
import { runHighlighterWithAstro } from '../../node_modules/@astrojs/prism/dist/highlighter.js';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
code: string;
|
code: string;
|
||||||
hints: string[];
|
hints: string[];
|
||||||
|
file?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { code, hints } = Astro.props;
|
const { code, hints, file } = Astro.props;
|
||||||
const id = `dv-${Math.random().toString(36).substring(2, 9)}`;
|
const id = `dv-${Math.random().toString(36).substring(2, 9)}`;
|
||||||
|
|
||||||
const lines = code.split('\n');
|
const originalLines = code.split('\n');
|
||||||
|
const fileExtension = (file?.split('/').pop() || 'file').split('.').pop() || '';
|
||||||
|
|
||||||
|
const langMap: Record<string, string> = {
|
||||||
|
js: 'javascript', jsx: 'jsx', ts: 'typescript', tsx: 'tsx',
|
||||||
|
html: 'html', css: 'css', json: 'json', py: 'python',
|
||||||
|
rb: 'ruby', java: 'java', c: 'c', cpp: 'cpp', cs: 'csharp',
|
||||||
|
go: 'go', rs: 'rust', php: 'php', swift: 'swift', kt: 'kotlin',
|
||||||
|
sh: 'bash', yaml: 'yaml', yml: 'yaml', md: 'markdown', sql: 'sql',
|
||||||
|
xml: 'xml', dockerfile: 'dockerfile', gitignore: 'bash', astro: 'astro',
|
||||||
|
};
|
||||||
|
const language = langMap[fileExtension] || undefined;
|
||||||
|
|
||||||
|
let highlightedLines: string[] = [];
|
||||||
|
if (language) {
|
||||||
|
try {
|
||||||
|
const { html } = runHighlighterWithAstro(language, code);
|
||||||
|
const parsed = html.split('\n');
|
||||||
|
if (parsed.length > originalLines.length && parsed[parsed.length - 1] === '') {
|
||||||
|
parsed.pop();
|
||||||
|
}
|
||||||
|
highlightedLines = parsed.length === originalLines.length ? parsed : [];
|
||||||
|
} catch {
|
||||||
|
// Fallback: no highlighting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const highlightedMap = new Map<string, string>();
|
||||||
|
highlightedLines.forEach((hl, i) => {
|
||||||
|
highlightedMap.set(String(i + 1), hl);
|
||||||
|
});
|
||||||
|
|
||||||
|
const lineContentMap = new Map<string, string>();
|
||||||
|
originalLines.forEach((line, i) => {
|
||||||
|
const lineNum = String(i + 1);
|
||||||
|
lineContentMap.set(lineNum, highlightedMap.get(lineNum) || line);
|
||||||
|
});
|
||||||
---
|
---
|
||||||
|
|
||||||
<div id={id} class="diff-viewer-container" data-dv-id={id}>
|
<div id={id} class="diff-viewer-container" data-dv-id={id} data-original-code={code} data-file-extension={fileExtension}>
|
||||||
<div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden">
|
<div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden">
|
||||||
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]">
|
<!-- Toolbar -->
|
||||||
<span class="text-sm text-[#8b949e]">{Astro.url.pathname.split('/').pop() || 'file.js'}</span>
|
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="text-sm text-[#8b949e]">{file?.split('/').pop() || 'file'}.{fileExtension}</span>
|
||||||
|
<span class="text-xs text-[#484f58]">|</span>
|
||||||
|
<span class="text-xs text-[#8b949e]">
|
||||||
|
<span id="left-lines-count">{originalLines.length}</span> lines
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button id="reveal-hints-btn" class="text-xs px-2 py-1 rounded bg-[#21262d] text-[#8b949e] hover:text-[#c9d1d9] hover:bg-[#30363d] transition-colors focus:outline-none focus:ring-0">
|
||||||
|
Reveal Hints
|
||||||
|
</button>
|
||||||
|
<button id="reset-btn" class="text-xs px-2 py-1 rounded bg-[#21262d] text-[#8b949e] hover:text-[#c9d1d9] hover:bg-[#30363d] transition-colors focus:outline-none focus:ring-0">
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<!-- Side-by-side viewer -->
|
||||||
<table class="w-full font-mono text-sm">
|
<div class="flex flex-col md:flex-row min-h-[400px]">
|
||||||
<tbody>
|
<!-- Left panel: Original code -->
|
||||||
{lines.map((line, index) => {
|
<div class="flex-1 flex flex-col border-b md:border-b-0 md:border-r border-[#30363d]">
|
||||||
|
<div class="bg-[#161b22] px-4 py-2 border-b border-[#30363d] flex items-center justify-between">
|
||||||
|
<span class="text-xs font-semibold text-[#f85149] uppercase tracking-wider">Original (Buggy)</span>
|
||||||
|
<span class="text-xs text-[#484f58]">Read-only</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-auto relative">
|
||||||
|
<div class="flex min-h-full">
|
||||||
|
<!-- Line numbers -->
|
||||||
|
<div class="left-gutter select-none bg-[#0d1117] border-r border-[#30363d] py-2 sticky left-0 z-10">
|
||||||
|
{originalLines.map((_, index) => {
|
||||||
|
const lineNum = index + 1;
|
||||||
|
const hasHint = hints.some(h => h.includes(`Line ${lineNum}`));
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-line={lineNum}
|
||||||
|
data-side="left"
|
||||||
|
class={`line-gutter-row font-mono text-right pr-3 pl-3 text-[#484f58] text-sm cursor-pointer hover:text-[#8b949e] transition-colors leading-[1.6] ${hasHint ? 'has-hint' : ''}`}
|
||||||
|
title={`Mark line ${lineNum} as buggy`}
|
||||||
|
>
|
||||||
|
{lineNum}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<!-- Code content -->
|
||||||
|
<div class="left-code-area flex-1 py-2 overflow-hidden">
|
||||||
|
{originalLines.map((line, index) => {
|
||||||
const lineNum = index + 1;
|
const lineNum = index + 1;
|
||||||
const hasHint = hints.some(h => h.includes(`Line ${lineNum}`));
|
const hasHint = hints.some(h => h.includes(`Line ${lineNum}`));
|
||||||
const hintText = hints.find(h => h.includes(`Line ${lineNum}`));
|
const hintText = hints.find(h => h.includes(`Line ${lineNum}`));
|
||||||
|
const content = highlightedMap.get(String(lineNum)) || line || ' ';
|
||||||
return (
|
return (
|
||||||
<tr
|
<div
|
||||||
data-line={lineNum}
|
data-line={lineNum}
|
||||||
class="group hover:bg-[#161b22] cursor-pointer transition-colors"
|
data-side="left"
|
||||||
title={`Mark line ${lineNum} as buggy`}
|
class={`code-row flex items-start gap-0 px-4 text-sm leading-[1.6] transition-colors ${hasHint ? 'has-hint' : ''}`}
|
||||||
>
|
>
|
||||||
<td class="w-16 text-right pr-4 select-none">
|
<span class={`text-[#484f58] w-0 select-none ${hasHint ? 'has-hint-num' : ''}`}></span>
|
||||||
<span class={`text-[#8b949e] line-number ${hasHint ? 'has-hint-color' : ''}`}>
|
<span class={`code-text font-mono text-[#e6edf3] whitespace-pre ${hasHint ? 'has-hint-bg' : ''}`} set:html={content}></span>
|
||||||
{lineNum}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 py-0.5 whitespace-pre">
|
|
||||||
<span class={`text-[#e6edf3] ${hasHint ? 'has-hint-bg bg-[#ff7b7233] px-2 -mx-2' : ''}`}>
|
|
||||||
{line || ' '}
|
|
||||||
</span>
|
|
||||||
{hasHint && (
|
{hasHint && (
|
||||||
<span class="ml-2 hint text-[#79c0ff] text-xs hidden">
|
<span class="hint-text font-mono text-[#79c0ff] text-xs ml-2 hidden">
|
||||||
• {hintText?.replace(`Line ${lineNum}: `, '')}
|
{hintText?.replace(`Line ${lineNum}: `, '')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Right panel: Editable code -->
|
||||||
|
<div class="flex-1 flex flex-col">
|
||||||
|
<div class="bg-[#161b22] px-4 py-2 border-b border-[#30363d] flex items-center justify-between">
|
||||||
|
<span class="text-xs font-semibold text-[#3fb950] uppercase tracking-wider">Your Fix</span>
|
||||||
|
<span class="text-xs text-[#484f58]">Editable</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-auto relative">
|
||||||
|
<div class="flex min-h-full">
|
||||||
|
<!-- Line numbers -->
|
||||||
|
<div class="right-gutter select-none bg-[#0d1117] border-r border-[#30363d] py-2 sticky left-0 z-10">
|
||||||
|
{originalLines.map((_, index) => (
|
||||||
|
<div
|
||||||
|
data-line={index + 1}
|
||||||
|
data-side="right"
|
||||||
|
class="right-gutter-row font-mono text-right pr-3 pl-3 text-[#484f58] text-sm leading-[1.6] bg-transparent"
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<!-- Editable content with diff overlay -->
|
||||||
|
<div class="right-editor-area flex-1 relative py-0">
|
||||||
|
<!-- Diff highlight overlay -->
|
||||||
|
<div id="diff-highlight" class="absolute inset-0 py-2 pointer-events-none">
|
||||||
|
{originalLines.map((_, index) => {
|
||||||
|
const lineNum = index + 1;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-diff-line={lineNum}
|
||||||
|
class="diff-row h-[1.6em] px-4 transition-colors"
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<!-- Textarea for editing -->
|
||||||
|
<textarea
|
||||||
|
id="edit-textarea"
|
||||||
|
class="editor-textarea absolute inset-0 w-full h-full bg-transparent text-[#e6edf3] font-mono text-sm leading-[1.6] resize-none focus:outline-none pl-4 pr-4 py-2"
|
||||||
|
spellcheck="false"
|
||||||
|
>{code}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status bar -->
|
||||||
|
<div class="bg-[#161b22] px-4 py-2 border-t border-[#30363d] flex items-center justify-between text-xs">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<span class="text-[#484f58]">
|
||||||
|
<span id="diff-added" class="text-[#3fb950]">0</span> added
|
||||||
|
</span>
|
||||||
|
<span class="text-[#484f58]">
|
||||||
|
<span id="diff-removed" class="text-[#f85149]">0</span> removed
|
||||||
|
</span>
|
||||||
|
<span class="text-[#484f58]">
|
||||||
|
<span id="diff-changed" class="text-[#d29922]">0</span> changed
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[#484f58]">
|
||||||
|
Click lines on the left to mark as buggy
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
tr {
|
.left-gutter-row,
|
||||||
|
.right-gutter-row {
|
||||||
|
height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-row {
|
||||||
|
height: 1.6em;
|
||||||
|
min-height: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea {
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
tab-size: 2;
|
||||||
|
-moz-tab-size: 2;
|
||||||
}
|
}
|
||||||
tr:hover {
|
|
||||||
background-color: #161b22;
|
.diff-row {
|
||||||
|
height: 1.6em;
|
||||||
|
min-height: 1.6em;
|
||||||
}
|
}
|
||||||
td:first-child {
|
|
||||||
border-right: 1px solid #30363d;
|
.diff-row.added {
|
||||||
|
background-color: #3fb95018;
|
||||||
}
|
}
|
||||||
tr.bugged td:first-child {
|
|
||||||
border-left: 3px solid #f85149;
|
.diff-row.removed {
|
||||||
}
|
|
||||||
tr.bugged .line-number {
|
|
||||||
color: #f85149;
|
|
||||||
}
|
|
||||||
tr.bugged {
|
|
||||||
background-color: #f8514918;
|
background-color: #f8514918;
|
||||||
}
|
}
|
||||||
.diff-viewer-container:not(.revealed) tr.bugged .line-number.has-hint-color {
|
|
||||||
color: #f85149 !important;
|
.code-row.diff-removed {
|
||||||
|
background-color: #f8514918;
|
||||||
}
|
}
|
||||||
.diff-viewer-container:not(.revealed) tr:not(.bugged) span.line-number.has-hint-color {
|
|
||||||
color: #8b949e !important;
|
.code-row.diff-added {
|
||||||
font-weight: normal !important;
|
background-color: #3fb95018;
|
||||||
}
|
}
|
||||||
.diff-viewer-container.revealed tr.bugged .hint {
|
|
||||||
|
.diff-viewer-container:not(.revealed) .has-hint .code-text {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-viewer-container.revealed .has-hint .hint-text {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
.diff-viewer-container:not(.revealed) td > span.has-hint-bg {
|
|
||||||
background-color: transparent !important;
|
.has-hint .code-text {
|
||||||
|
background-color: #f8514918;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-hint.has-hint-bg .code-text {
|
||||||
|
background-color: #f8514918;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-hint .has-hint-num {
|
||||||
|
color: #f85149 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-hint:not(.revealed) .has-hint-num {
|
||||||
|
color: #484f58 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-viewer-container.revealed .has-hint .has-hint-num {
|
||||||
|
color: #f85149 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
.overflow-auto::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-auto::-webkit-scrollbar-track {
|
||||||
|
background: #0d1117;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-auto::-webkit-scrollbar-thumb {
|
||||||
|
background: #30363d;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overflow-auto::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #484f58;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script client:load>
|
<script client:load>
|
||||||
(() => {
|
(() => {
|
||||||
const el = document.querySelector('[data-dv-id]');
|
const container = document.querySelector('[data-dv-id]');
|
||||||
if (!el) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
const originalCode = container.getAttribute('data-original-code') || '';
|
||||||
|
const originalLines = originalCode.split('\n');
|
||||||
|
|
||||||
|
const leftGutter = container.querySelector('.left-gutter');
|
||||||
|
const leftCodeArea = container.querySelector('.left-code-area');
|
||||||
|
const rightGutter = container.querySelector('.right-gutter');
|
||||||
|
const rightEditorArea = container.querySelector('.right-editor-area');
|
||||||
|
const textarea = document.getElementById('edit-textarea');
|
||||||
|
const diffHighlight = document.getElementById('diff-highlight');
|
||||||
|
const revealBtn = document.getElementById('reveal-hints-btn');
|
||||||
|
const resetBtn = document.getElementById('reset-btn');
|
||||||
|
const addedEl = document.getElementById('diff-added');
|
||||||
|
const removedEl = document.getElementById('diff-removed');
|
||||||
|
const changedEl = document.getElementById('diff-changed');
|
||||||
|
|
||||||
const buggedLines = new Set();
|
const buggedLines = new Set();
|
||||||
|
|
||||||
function updateBuggedState() {
|
// Synchronized scrolling
|
||||||
const rows = el.querySelectorAll('tr[data-line]');
|
function syncScroll(source, targetGutter, targetCode) {
|
||||||
rows.forEach(row => {
|
if (targetCode) {
|
||||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
targetCode.scrollTop = source.scrollTop;
|
||||||
row.classList.toggle('bugged', buggedLines.has(lineNum));
|
targetCode.scrollLeft = source.scrollLeft;
|
||||||
|
}
|
||||||
|
if (targetGutter) {
|
||||||
|
targetGutter.scrollTop = source.scrollTop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left side scroll sync (also scrolls right side)
|
||||||
|
if (leftCodeArea) {
|
||||||
|
leftCodeArea.addEventListener('scroll', () => {
|
||||||
|
syncScroll(leftCodeArea, rightGutter, rightEditorArea);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
el.addEventListener('click', function(e) {
|
// Right side scroll sync (also scrolls left side)
|
||||||
const target = e.target;
|
if (rightEditorArea) {
|
||||||
if (!(target instanceof HTMLElement)) return;
|
rightEditorArea.addEventListener('scroll', () => {
|
||||||
const row = target.closest('tr[data-line]');
|
syncScroll(rightEditorArea, leftGutter, leftCodeArea);
|
||||||
if (!row) return;
|
});
|
||||||
|
|
||||||
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
|
||||||
if (buggedLines.has(lineNum)) {
|
|
||||||
buggedLines.delete(lineNum);
|
|
||||||
} else {
|
|
||||||
buggedLines.add(lineNum);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBuggedState();
|
// Diff computation using LCS-based line diff
|
||||||
|
function computeDiff(oldText, newText) {
|
||||||
|
const oldLines = oldText.split('\n');
|
||||||
|
const newLines = newText.split('\n');
|
||||||
|
|
||||||
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
// LCS to compute diff
|
||||||
|
const m = oldLines.length;
|
||||||
|
const n = newLines.length;
|
||||||
|
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
||||||
|
|
||||||
|
for (let i = 1; i <= m; i++) {
|
||||||
|
for (let j = 1; j <= n; j++) {
|
||||||
|
if (oldLines[i - 1] === newLines[j - 1]) {
|
||||||
|
dp[i][j] = dp[i - 1][j - 1] + 1;
|
||||||
|
} else {
|
||||||
|
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backtrack to get diff ops
|
||||||
|
const ops = [];
|
||||||
|
let i = m, j = n;
|
||||||
|
while (i > 0 || j > 0) {
|
||||||
|
if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
|
||||||
|
ops.push({ type: 'equal', oldLine: i, newLine: j });
|
||||||
|
i--; j--;
|
||||||
|
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
|
||||||
|
ops.push({ type: 'added', newLine: j });
|
||||||
|
j--;
|
||||||
|
} else {
|
||||||
|
ops.push({ type: 'removed', oldLine: i });
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ops.reverse();
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDiffHighlight() {
|
||||||
|
if (!textarea || !diffHighlight) return;
|
||||||
|
|
||||||
|
const newText = textarea.value;
|
||||||
|
const allOld = originalLines;
|
||||||
|
const allNew = newText.split('\n');
|
||||||
|
const totalLines = Math.max(allOld.length, allNew.length);
|
||||||
|
|
||||||
|
let added = 0, removed = 0, changed = 0;
|
||||||
|
|
||||||
|
// Rebuild diff rows to match textarea line count
|
||||||
|
diffHighlight.innerHTML = '';
|
||||||
|
for (let i = 0; i < totalLines; i++) {
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.setAttribute('data-diff-line', String(i + 1));
|
||||||
|
row.className = 'diff-row h-[1.6em] px-4 transition-colors';
|
||||||
|
diffHighlight.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare line by line using LCS-based approach
|
||||||
|
let oi = 0, ni = 0;
|
||||||
|
const statusMap = new Map(); // old line index -> 'equal' | 'removed' | 'changed'
|
||||||
|
|
||||||
|
while (oi < allOld.length || ni < allNew.length) {
|
||||||
|
if (oi < allOld.length && ni < allNew.length && allOld[oi] === allNew[ni]) {
|
||||||
|
statusMap.set(oi, 'equal');
|
||||||
|
oi++;
|
||||||
|
ni++;
|
||||||
|
} else if (oi < allOld.length && ni < allNew.length) {
|
||||||
|
statusMap.set(oi, 'changed');
|
||||||
|
added++;
|
||||||
|
changed++;
|
||||||
|
oi++;
|
||||||
|
ni++;
|
||||||
|
} else if (oi < allOld.length) {
|
||||||
|
statusMap.set(oi, 'removed');
|
||||||
|
removed++;
|
||||||
|
oi++;
|
||||||
|
} else {
|
||||||
|
added++;
|
||||||
|
ni++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply to right side diff overlay
|
||||||
|
statusMap.forEach((status, idx) => {
|
||||||
|
const lineNum = idx + 1;
|
||||||
|
const row = diffHighlight.querySelector(`[data-diff-line="${lineNum}"]`);
|
||||||
|
if (row) {
|
||||||
|
if (status === 'removed') {
|
||||||
|
row.classList.add('removed');
|
||||||
|
} else if (status === 'changed') {
|
||||||
|
row.classList.add('added');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply to left panel code rows (only removed = red, no yellow)
|
||||||
|
statusMap.forEach((status, idx) => {
|
||||||
|
const lineNum = idx + 1;
|
||||||
|
const row = leftCodeArea?.querySelector(`.code-row[data-line="${lineNum}"]`);
|
||||||
|
if (row) {
|
||||||
|
row.classList.remove('diff-removed', 'diff-added');
|
||||||
|
if (status === 'removed') {
|
||||||
|
row.classList.add('diff-removed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Highlight added lines beyond original length
|
||||||
|
for (let i = allOld.length; i < allNew.length; i++) {
|
||||||
|
const row = diffHighlight.querySelector(`[data-diff-line="${i + 1}"]`);
|
||||||
|
if (row) {
|
||||||
|
row.classList.add('added');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addedEl.textContent = added;
|
||||||
|
removedEl.textContent = removed;
|
||||||
|
changedEl.textContent = changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left panel: click to mark bugged lines (gutter + code area)
|
||||||
|
function toggleBuggedLine(lineNum) {
|
||||||
|
if (isNaN(lineNum) || lineNum === 0) return;
|
||||||
|
|
||||||
|
const gutterRow = leftGutter?.querySelector(`[data-line="${lineNum}"][data-side="left"]`);
|
||||||
|
const codeRow = leftCodeArea?.querySelector(`.code-row[data-line="${lineNum}"]`);
|
||||||
|
|
||||||
|
const isBugged = buggedLines.has(lineNum);
|
||||||
|
|
||||||
|
if (isBugged) {
|
||||||
|
buggedLines.delete(lineNum);
|
||||||
|
if (gutterRow) { gutterRow.style.backgroundColor = ''; gutterRow.style.borderLeft = ''; }
|
||||||
|
if (codeRow) { codeRow.style.backgroundColor = ''; }
|
||||||
|
} else {
|
||||||
|
buggedLines.add(lineNum);
|
||||||
|
if (gutterRow) { gutterRow.style.backgroundColor = '#f8514920'; gutterRow.style.borderLeft = '3px solid #f85149'; }
|
||||||
|
if (codeRow) { codeRow.style.backgroundColor = '#f8514920'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
container.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
||||||
}));
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
leftGutter?.addEventListener('click', (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
if (!(target instanceof HTMLElement)) return;
|
||||||
|
const row = target.closest('[data-line][data-side="left"]');
|
||||||
|
if (!row) return;
|
||||||
|
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||||
|
toggleBuggedLine(lineNum);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
leftCodeArea?.addEventListener('click', (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
if (!(target instanceof HTMLElement)) return;
|
||||||
|
const row = target.closest('.code-row');
|
||||||
|
if (!row) return;
|
||||||
|
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
||||||
|
toggleBuggedLine(lineNum);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reveal hints
|
||||||
|
revealBtn?.addEventListener('click', () => {
|
||||||
|
const isRevealed = container.classList.toggle('revealed');
|
||||||
|
revealBtn.textContent = isRevealed ? 'Hide Hints' : 'Reveal Hints';
|
||||||
|
revealBtn.classList.toggle('text-[#79c0ff]', isRevealed);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
resetBtn?.addEventListener('click', () => {
|
||||||
|
textarea.value = originalLines.join('\n');
|
||||||
|
buggedLines.clear();
|
||||||
|
container.classList.remove('revealed');
|
||||||
|
revealBtn.textContent = 'Reveal Hints';
|
||||||
|
revealBtn.classList.remove('text-[#79c0ff]');
|
||||||
|
|
||||||
|
const gutterRows = leftGutter?.querySelectorAll('[data-side="left"]');
|
||||||
|
gutterRows?.forEach(row => {
|
||||||
|
row.style.backgroundColor = '';
|
||||||
|
row.style.borderLeft = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const codeRows = leftCodeArea?.querySelectorAll('.code-row');
|
||||||
|
codeRows?.forEach(row => {
|
||||||
|
row.style.backgroundColor = '';
|
||||||
|
row.classList.remove('diff-removed', 'diff-added');
|
||||||
|
});
|
||||||
|
|
||||||
|
updateDiffHighlight();
|
||||||
|
|
||||||
|
container.dispatchEvent(new CustomEvent('bugged-line-reset', {
|
||||||
|
bubbles: true,
|
||||||
|
detail: { allBugged: [] }
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Textarea input -> update diff
|
||||||
|
textarea?.addEventListener('input', () => {
|
||||||
|
updateDiffHighlight();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tab key support in textarea
|
||||||
|
textarea?.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
const end = textarea.selectionEnd;
|
||||||
|
textarea.value = textarea.value.substring(0, start) + ' ' + textarea.value.substring(end);
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = start + 2;
|
||||||
|
updateDiffHighlight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial diff
|
||||||
|
updateDiffHighlight();
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -76,46 +76,35 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Code Viewer -->
|
<!-- Code Viewer -->
|
||||||
<DiffViewer code={challenge.code} hints={challenge.hints} />
|
<DiffViewer code={challenge.code} hints={challenge.hints} file={challenge.file} />
|
||||||
|
|
||||||
<!-- Fix Submission -->
|
<!-- Fix Submission -->
|
||||||
<div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden">
|
<div id="fix-panel" class="mt-6 bg-[#161b22] border border-[#30363d] rounded-md overflow-hidden">
|
||||||
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between">
|
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center justify-between">
|
||||||
<h2 class="text-lg font-semibold text-[#c9d1d9]">Submit Your Fix</h2>
|
<h2 class="text-lg font-semibold text-[#c9d1d9]">Submit Your Fix</h2>
|
||||||
<div id="bugged-count" class="text-sm text-[#8b949e]">
|
<div class="text-sm text-[#8b949e]">
|
||||||
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold">0</span>
|
Lines marked: <span id="bugged-count-num" class="text-[#f85149] font-semibold">0</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Marked lines summary -->
|
|
||||||
<div id="marked-lines-summary" class="px-4 py-3 border-b border-[#30363d] hidden">
|
|
||||||
<p class="text-sm text-[#8b949e] mb-2">Marked lines:</p>
|
|
||||||
<div id="marked-lines-tags" class="flex flex-wrap gap-2"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Inline fix editor -->
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label class="block text-sm font-medium text-[#c9d1d9] mb-2">
|
<p class="text-sm text-[#8b949e] mb-1">
|
||||||
Line-by-line fix:
|
Edit the code on the <span class="text-[#3fb950] font-medium">right panel</span> above to fix the bugs.
|
||||||
</label>
|
Changes are highlighted in real-time.
|
||||||
<p class="text-xs text-[#8b949e] mb-3">Click a marked line number below to edit its fix.</p>
|
</p>
|
||||||
<div id="fix-editor-container" class="space-y-2">
|
<p class="text-xs text-[#484f58]">
|
||||||
<p id="fix-editor-placeholder" class="text-sm text-[#484f58] italic">Click on lines in the diff above to mark them, then add your fixes here.</p>
|
<span class="text-[#f85149]">■</span> Removed
|
||||||
</div>
|
<span class="text-[#d29922]">■</span> Changed
|
||||||
|
<span class="text-[#3fb950]">■</span> Added
|
||||||
|
Click lines on the left to mark buggy lines
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Unified diff textarea -->
|
<!-- Bugged lines summary -->
|
||||||
<div class="mb-4">
|
<div id="marked-lines-summary" class="mb-4 hidden">
|
||||||
<label class="block text-sm font-medium text-[#c9d1d9] mb-2">
|
<p class="text-sm font-medium text-[#c9d1d9] mb-2">Marked buggy lines:</p>
|
||||||
Or provide as unified diff:
|
<div id="marked-lines-tags" class="flex flex-wrap gap-2"></div>
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="diff-textarea"
|
|
||||||
placeholder="- old line + new line"
|
|
||||||
rows={6}
|
|
||||||
class="w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
|
|
@ -137,15 +126,30 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
|
||||||
<script client:load>
|
<script client:load>
|
||||||
(() => {
|
(() => {
|
||||||
const buggedLines = new Set();
|
const buggedLines = new Set();
|
||||||
const fixEditorContainer = document.getElementById('fix-editor-container');
|
|
||||||
const markedLinesTags = document.getElementById('marked-lines-tags');
|
const markedLinesTags = document.getElementById('marked-lines-tags');
|
||||||
const markedLinesSummary = document.getElementById('marked-lines-summary');
|
const markedLinesSummary = document.getElementById('marked-lines-summary');
|
||||||
const buggedCountNum = document.getElementById('bugged-count-num');
|
const buggedCountNum = document.getElementById('bugged-count-num');
|
||||||
const submitBtn = document.getElementById('submit-fix-btn');
|
const submitBtn = document.getElementById('submit-fix-btn');
|
||||||
const clearBtn = document.getElementById('clear-all-btn');
|
const clearBtn = document.getElementById('clear-all-btn');
|
||||||
const diffTextarea = document.getElementById('diff-textarea');
|
|
||||||
const submissionMessage = document.getElementById('submission-message');
|
const submissionMessage = document.getElementById('submission-message');
|
||||||
const fixEditorPlaceholder = document.getElementById('fix-editor-placeholder');
|
|
||||||
|
function toRanges(sorted) {
|
||||||
|
const ranges = [];
|
||||||
|
if (sorted.length === 0) return ranges;
|
||||||
|
let start = sorted[0];
|
||||||
|
let end = sorted[0];
|
||||||
|
for (let i = 1; i < sorted.length; i++) {
|
||||||
|
if (sorted[i] === end + 1) {
|
||||||
|
end = sorted[i];
|
||||||
|
} else {
|
||||||
|
ranges.push([start, end]);
|
||||||
|
start = sorted[i];
|
||||||
|
end = sorted[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges.push([start, end]);
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
function updateUI() {
|
function updateUI() {
|
||||||
const arr = [...buggedLines].sort((a, b) => a - b);
|
const arr = [...buggedLines].sort((a, b) => a - b);
|
||||||
|
|
@ -153,13 +157,11 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
|
||||||
|
|
||||||
if (markedLinesTags) {
|
if (markedLinesTags) {
|
||||||
markedLinesTags.innerHTML = '';
|
markedLinesTags.innerHTML = '';
|
||||||
arr.forEach(lineNum => {
|
const ranges = toRanges(arr);
|
||||||
|
ranges.forEach(([start, end]) => {
|
||||||
const tag = document.createElement('span');
|
const tag = document.createElement('span');
|
||||||
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
|
tag.className = 'inline-flex items-center gap-1 bg-[#f8514920] text-[#f85149] text-xs px-2 py-0.5 rounded cursor-pointer hover:bg-[#f8514930] transition-colors';
|
||||||
tag.textContent = `Line ${lineNum}`;
|
tag.textContent = start === end ? `Line ${start}` : `Lines ${start}-${end}`;
|
||||||
tag.addEventListener('click', () => {
|
|
||||||
showLineFixEditor(lineNum);
|
|
||||||
});
|
|
||||||
markedLinesTags.appendChild(tag);
|
markedLinesTags.appendChild(tag);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -168,58 +170,10 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
|
||||||
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
|
markedLinesSummary.classList.toggle('hidden', arr.length === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fixEditorPlaceholder) {
|
// Check if the editor has any changes
|
||||||
fixEditorPlaceholder.classList.toggle('hidden', arr.length > 0);
|
const textarea = document.getElementById('edit-textarea');
|
||||||
}
|
const editorChanged = textarea ? textarea.value !== textarea.defaultValue : false;
|
||||||
|
submitBtn.disabled = arr.length === 0 && !editorChanged;
|
||||||
if (submitBtn) {
|
|
||||||
submitBtn.disabled = arr.length === 0 && !diffTextarea?.value.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLineFixEditor(lineNum) {
|
|
||||||
if (!fixEditorContainer) return;
|
|
||||||
|
|
||||||
const existing = document.getElementById(`fix-editor-${lineNum}`);
|
|
||||||
if (existing) {
|
|
||||||
existing.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
existing.querySelector('textarea')?.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const editorDiv = document.createElement('div');
|
|
||||||
editorDiv.id = `fix-editor-${lineNum}`;
|
|
||||||
editorDiv.className = 'bg-[#0d1117] border border-[#30363d] rounded-md p-3';
|
|
||||||
|
|
||||||
const header = document.createElement('div');
|
|
||||||
header.className = 'flex items-center justify-between mb-2';
|
|
||||||
|
|
||||||
const label = document.createElement('span');
|
|
||||||
label.className = 'text-sm font-medium text-[#f85149]';
|
|
||||||
label.textContent = `Line ${lineNum} — Your fix:`;
|
|
||||||
|
|
||||||
const removeBtn = document.createElement('button');
|
|
||||||
removeBtn.className = 'text-xs text-[#484f58] hover:text-[#f85149] transition-colors';
|
|
||||||
removeBtn.textContent = '✕';
|
|
||||||
removeBtn.addEventListener('click', () => {
|
|
||||||
buggedLines.delete(lineNum);
|
|
||||||
editorDiv.remove();
|
|
||||||
updateUI();
|
|
||||||
});
|
|
||||||
|
|
||||||
header.appendChild(label);
|
|
||||||
header.appendChild(removeBtn);
|
|
||||||
|
|
||||||
const textarea = document.createElement('textarea');
|
|
||||||
textarea.rows = 3;
|
|
||||||
textarea.className = 'w-full bg-[#0d1117] border border-[#30363d] text-[#c9d1d9] rounded px-2 py-1.5 focus:outline-none focus:ring-2 focus:ring-[#58a6ff] focus:border-transparent text-sm font-mono';
|
|
||||||
textarea.placeholder = 'Enter the corrected line...';
|
|
||||||
textarea.addEventListener('input', updateUI);
|
|
||||||
|
|
||||||
editorDiv.appendChild(header);
|
|
||||||
editorDiv.appendChild(textarea);
|
|
||||||
|
|
||||||
fixEditorContainer.appendChild(editorDiv);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for bugged-line-toggle events from DiffViewer
|
// Listen for bugged-line-toggle events from DiffViewer
|
||||||
|
|
@ -227,57 +181,57 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
|
||||||
const detail = e.detail;
|
const detail = e.detail;
|
||||||
if (detail.bugged) {
|
if (detail.bugged) {
|
||||||
buggedLines.add(detail.line);
|
buggedLines.add(detail.line);
|
||||||
showLineFixEditor(detail.line);
|
|
||||||
} else {
|
} else {
|
||||||
buggedLines.delete(detail.line);
|
buggedLines.delete(detail.line);
|
||||||
const editor = document.getElementById(`fix-editor-${detail.line}`);
|
|
||||||
if (editor) editor.remove();
|
|
||||||
}
|
}
|
||||||
updateUI();
|
updateUI();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Diff textarea input
|
// Listen for bugged-line-reset events from DiffViewer
|
||||||
if (diffTextarea) {
|
document.addEventListener('bugged-line-reset', (e) => {
|
||||||
diffTextarea.addEventListener('input', updateUI);
|
buggedLines.clear();
|
||||||
}
|
updateUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for textarea changes
|
||||||
|
document.getElementById('edit-textarea')?.addEventListener('input', () => {
|
||||||
|
updateUI();
|
||||||
|
});
|
||||||
|
|
||||||
// Submit
|
// Submit
|
||||||
if (submitBtn) {
|
if (submitBtn) {
|
||||||
submitBtn.addEventListener('click', () => {
|
submitBtn.addEventListener('click', () => {
|
||||||
const lineFixes = {};
|
const textarea = document.getElementById('edit-textarea');
|
||||||
buggedLines.forEach(lineNum => {
|
const fixedCode = textarea?.value.trim() || '';
|
||||||
const editor = document.getElementById(`fix-editor-${lineNum}`);
|
const originalCode = textarea?.defaultValue || '';
|
||||||
if (editor) {
|
|
||||||
const ta = editor.querySelector('textarea');
|
|
||||||
if (ta?.value.trim()) {
|
|
||||||
lineFixes[lineNum] = ta.value.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const unifiedDiff = diffTextarea?.value.trim() || '';
|
if (fixedCode === originalCode) {
|
||||||
|
|
||||||
if (Object.keys(lineFixes).length === 0 && !unifiedDiff) {
|
|
||||||
if (submissionMessage) {
|
if (submissionMessage) {
|
||||||
submissionMessage.textContent = 'Please mark some lines or provide a diff.';
|
submissionMessage.textContent = 'Please make some changes to the code before submitting.';
|
||||||
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
|
submissionMessage.className = 'mt-3 text-sm text-[#f85149]';
|
||||||
submissionMessage.classList.remove('hidden');
|
submissionMessage.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reveal bugged lines and hints in the diff viewer
|
// Collect marked lines
|
||||||
const dvContainer = document.querySelector('.diff-viewer-container');
|
const markedArr = [...buggedLines].sort((a, b) => a - b);
|
||||||
if (dvContainer) {
|
|
||||||
dvContainer.classList.add('revealed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show success message
|
// Show success message
|
||||||
if (submissionMessage) {
|
if (submissionMessage) {
|
||||||
submissionMessage.textContent = 'Fix submitted! (Backend integration coming soon)';
|
const lineInfo = markedArr.length > 0
|
||||||
|
? ` Marked ${markedArr.length} buggy line(s).`
|
||||||
|
: ' No lines were specifically marked as buggy.';
|
||||||
|
submissionMessage.textContent = `Fix submitted!${lineInfo} (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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the fix for debugging
|
||||||
|
console.log('=== Challenge Fix Submitted ===');
|
||||||
|
console.log('Marked buggy lines:', markedArr);
|
||||||
|
console.log('Fixed code:\n', fixedCode);
|
||||||
|
console.log('===============================');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,20 +239,12 @@ const stars = '★'.repeat(challenge.difficulty) + '☆'.repeat(5 - challenge.di
|
||||||
if (clearBtn) {
|
if (clearBtn) {
|
||||||
clearBtn.addEventListener('click', () => {
|
clearBtn.addEventListener('click', () => {
|
||||||
buggedLines.clear();
|
buggedLines.clear();
|
||||||
const editors = fixEditorContainer?.querySelectorAll('[id^="fix-editor-"]');
|
|
||||||
editors?.forEach(e => e.remove());
|
|
||||||
if (diffTextarea) diffTextarea.value = '';
|
|
||||||
if (submissionMessage) submissionMessage.classList.add('hidden');
|
if (submissionMessage) submissionMessage.classList.add('hidden');
|
||||||
updateUI();
|
updateUI();
|
||||||
|
|
||||||
// Reset bugged state and revealed state in DiffViewer
|
// Trigger reset on DiffViewer
|
||||||
const dvContainer = document.querySelector('.diff-viewer-container');
|
const resetBtn = document.getElementById('reset-btn');
|
||||||
if (dvContainer) {
|
if (resetBtn) resetBtn.click();
|
||||||
dvContainer.classList.remove('revealed');
|
|
||||||
}
|
|
||||||
document.querySelectorAll('tr.bugged').forEach(row => {
|
|
||||||
row.classList.remove('bugged');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ import Layout from '../layouts/Layout.astro';
|
||||||
<div class="relative w-36 h-36">
|
<div class="relative w-36 h-36">
|
||||||
<svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120">
|
<svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120">
|
||||||
<circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"/>
|
<circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"/>
|
||||||
<circle cx="60" cy="60" r="52" fill="none" stroke="#a5d6ff" stroke-width="10"
|
<circle cx="60" cy="60" r="52" fill="none" stroke="#58a6ff" stroke-width="10"
|
||||||
stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round"
|
stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round"
|
||||||
id="solved-ring" class="transition-all duration-700 ease-out"/>
|
id="solved-ring" class="transition-all duration-700 ease-out"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -69,7 +69,7 @@ import Layout from '../layouts/Layout.astro';
|
||||||
<div class="relative w-36 h-36">
|
<div class="relative w-36 h-36">
|
||||||
<svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120">
|
<svg class="w-36 h-36 transform -rotate-90" viewBox="0 0 120 120">
|
||||||
<circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"/>
|
<circle cx="60" cy="60" r="52" fill="none" stroke="#21262d" stroke-width="10"/>
|
||||||
<circle cx="60" cy="60" r="52" fill="none" stroke="#79c0ff" stroke-width="10"
|
<circle cx="60" cy="60" r="52" fill="none" stroke="#58a6ff" stroke-width="10"
|
||||||
stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round"
|
stroke-dasharray="326.73" stroke-dashoffset="326.73" stroke-linecap="round"
|
||||||
id="streak-ring" class="transition-all duration-700 ease-out"/>
|
id="streak-ring" class="transition-all duration-700 ease-out"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
|
@ -37,3 +37,37 @@ a {
|
||||||
a:hover {
|
a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prism syntax highlighting - GitHub Dark theme */
|
||||||
|
.code-text .token.comment,
|
||||||
|
.code-text .token.prolog,
|
||||||
|
.code-text .token.doctype,
|
||||||
|
.code-text .token.cdata { color: #8b949e; }
|
||||||
|
.code-text .token.punctuation { color: #e6edf3; }
|
||||||
|
.code-text .token.property,
|
||||||
|
.code-text .token.tag,
|
||||||
|
.code-text .token.boolean,
|
||||||
|
.code-text .token.number,
|
||||||
|
.code-text .token.constant,
|
||||||
|
.code-text .token.symbol,
|
||||||
|
.code-text .token.deleted { color: #79c0ff; }
|
||||||
|
.code-text .token.selector,
|
||||||
|
.code-text .token.attr-name,
|
||||||
|
.code-text .token.string,
|
||||||
|
.code-text .token.char,
|
||||||
|
.code-text .token.builtin,
|
||||||
|
.code-text .token.inserted { color: #a5d6ff; }
|
||||||
|
.code-text .token.operator,
|
||||||
|
.code-text .token.entity,
|
||||||
|
.code-text .token.url,
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string,
|
||||||
|
.code-text .token.variable { color: #ffa657; }
|
||||||
|
.code-text .token.atrule,
|
||||||
|
.code-text .token.attr-value,
|
||||||
|
.code-text .token.keyword { color: #ff7b72; }
|
||||||
|
.code-text .token.function,
|
||||||
|
.code-text .token.class-name { color: #d2a8ff; }
|
||||||
|
.code-text .token.regex,
|
||||||
|
.code-text .token.important,
|
||||||
|
.code-text .token.entity { color: #ff7b72; }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue