Skip to content

Commit 656fae7

Browse files
Kyle ParkerKyle Parker
authored andcommitted
Add fun beginner-friendly Flask web app for Python learning
1 parent 5606f59 commit 656fae7

File tree

8 files changed

+578
-1
lines changed

8 files changed

+578
-1
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ jobs:
1818
with:
1919
python-version: "3.12"
2020

21+
- name: Install dependencies
22+
run: pip install -r requirements.txt
23+
2124
- name: Run unit tests
2225
run: python -m unittest discover -s tests -p "test_*.py" -v

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[![Python](https://img.shields.io/badge/Python-3.10%2B-blue.svg)](https://www.python.org/)
55
[![CI](https://img.shields.io/badge/tests-unittest-green.svg)](.github/workflows/ci.yml)
66

7-
Code Quest is a beginner-friendly command-line learning game for Python and SQL. It uses interactive challenges, instant feedback, and progress tracking to help users build practical coding and query skills.
7+
Code Quest is a beginner-friendly learning game for Python and SQL. It now includes a fun web app for beginners with guided explanations, hints, and progress tracking.
88

99
![Code Quest Demo](assets/demo.gif)
1010

@@ -38,6 +38,15 @@ cd "/Users/kyleparker/Documents/code game"
3838
python3 main.py
3939
```
4040

41+
## Run the web app (recommended)
42+
```bash
43+
cd "/Users/kyleparker/Documents/code game"
44+
pip3 install -r requirements.txt
45+
python3 web_app.py
46+
```
47+
48+
Then open `http://127.0.0.1:5000` in your browser.
49+
4150
## Example game loop
4251
1. Pick Python or SQL challenge.
4352
2. Submit answer or SQL query.

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Flask==3.1.2

static/styles.css

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
:root {
2+
--bg-a: #0b1222;
3+
--bg-b: #111c35;
4+
--surface: #162645;
5+
--surface-2: #1c3158;
6+
--text: #eff5ff;
7+
--muted: #a8b9d6;
8+
--accent: #19d3a2;
9+
--accent-2: #ffb347;
10+
--danger: #ff7e79;
11+
}
12+
13+
* {
14+
box-sizing: border-box;
15+
}
16+
17+
body {
18+
margin: 0;
19+
min-height: 100vh;
20+
font-family: "Space Grotesk", sans-serif;
21+
color: var(--text);
22+
background: radial-gradient(circle at 10% 10%, #1d3d6d 0%, var(--bg-a) 45%, #080d18 100%);
23+
overflow-x: hidden;
24+
}
25+
26+
.bg-shape {
27+
position: fixed;
28+
border-radius: 50%;
29+
filter: blur(60px);
30+
z-index: 0;
31+
opacity: 0.35;
32+
}
33+
34+
.bg-shape-a {
35+
width: 320px;
36+
height: 320px;
37+
background: #26d0ce;
38+
top: -80px;
39+
left: -90px;
40+
}
41+
42+
.bg-shape-b {
43+
width: 260px;
44+
height: 260px;
45+
background: #ff9f43;
46+
right: -80px;
47+
bottom: -60px;
48+
}
49+
50+
.container {
51+
position: relative;
52+
z-index: 1;
53+
width: min(980px, 92vw);
54+
margin: 2rem auto 3rem;
55+
}
56+
57+
.hero,
58+
.card {
59+
background: linear-gradient(160deg, rgba(28, 49, 88, 0.95), rgba(17, 29, 54, 0.95));
60+
border: 1px solid rgba(120, 151, 206, 0.24);
61+
border-radius: 24px;
62+
box-shadow: 0 20px 45px rgba(0, 0, 0, 0.32);
63+
animation: rise 500ms ease;
64+
}
65+
66+
.hero {
67+
padding: 2rem;
68+
}
69+
70+
h1,
71+
h2,
72+
h3 {
73+
margin: 0;
74+
}
75+
76+
.lead {
77+
color: var(--muted);
78+
max-width: 68ch;
79+
}
80+
81+
.eyebrow {
82+
color: var(--accent-2);
83+
font-weight: 700;
84+
text-transform: uppercase;
85+
letter-spacing: 0.08em;
86+
font-size: 0.8rem;
87+
margin: 0 0 0.35rem;
88+
}
89+
90+
.stats {
91+
display: grid;
92+
grid-template-columns: repeat(3, minmax(0, 1fr));
93+
gap: 0.9rem;
94+
margin-top: 1.3rem;
95+
}
96+
97+
.stat {
98+
padding: 1rem;
99+
}
100+
101+
.stat span {
102+
color: var(--muted);
103+
font-size: 0.86rem;
104+
}
105+
106+
.stat strong {
107+
display: block;
108+
margin-top: 0.3rem;
109+
font-size: 1.5rem;
110+
}
111+
112+
.actions {
113+
display: flex;
114+
gap: 0.8rem;
115+
margin-top: 1.3rem;
116+
}
117+
118+
.btn {
119+
font: inherit;
120+
border: none;
121+
border-radius: 999px;
122+
padding: 0.7rem 1.2rem;
123+
cursor: pointer;
124+
text-decoration: none;
125+
display: inline-flex;
126+
align-items: center;
127+
justify-content: center;
128+
transition: transform 120ms ease, opacity 120ms ease;
129+
}
130+
131+
.btn:hover {
132+
transform: translateY(-1px);
133+
}
134+
135+
.btn-primary {
136+
background: linear-gradient(120deg, var(--accent), #18b0c3);
137+
color: #03171a;
138+
font-weight: 700;
139+
}
140+
141+
.btn-ghost {
142+
background: transparent;
143+
color: var(--text);
144+
border: 1px solid rgba(173, 198, 241, 0.35);
145+
}
146+
147+
.grid {
148+
margin-top: 1rem;
149+
display: grid;
150+
grid-template-columns: repeat(3, minmax(0, 1fr));
151+
gap: 0.9rem;
152+
}
153+
154+
.feature {
155+
padding: 1.1rem;
156+
}
157+
158+
.feature p {
159+
color: var(--muted);
160+
}
161+
162+
.back-link {
163+
color: #b9cdf2;
164+
text-decoration: none;
165+
display: inline-block;
166+
margin: 0.2rem 0 0.8rem;
167+
}
168+
169+
.lesson {
170+
padding: 1.3rem;
171+
}
172+
173+
.challenge pre {
174+
background: #0a1326;
175+
border: 1px solid rgba(128, 158, 213, 0.32);
176+
border-radius: 12px;
177+
padding: 1rem;
178+
white-space: pre-wrap;
179+
font-family: "JetBrains Mono", monospace;
180+
color: #d4e4ff;
181+
}
182+
183+
.answer-form {
184+
display: grid;
185+
gap: 0.6rem;
186+
}
187+
188+
.answer-form input {
189+
width: 100%;
190+
border: 1px solid rgba(130, 160, 216, 0.45);
191+
background: #0c1730;
192+
color: var(--text);
193+
border-radius: 10px;
194+
padding: 0.8rem;
195+
font-family: "JetBrains Mono", monospace;
196+
}
197+
198+
.attempts {
199+
color: var(--muted);
200+
font-size: 0.92rem;
201+
}
202+
203+
.feedback {
204+
margin-top: 1rem;
205+
border-radius: 14px;
206+
padding: 1rem;
207+
}
208+
209+
.feedback.success {
210+
background: rgba(25, 211, 162, 0.12);
211+
border: 1px solid rgba(25, 211, 162, 0.45);
212+
}
213+
214+
.feedback.retry {
215+
background: rgba(255, 179, 71, 0.12);
216+
border: 1px solid rgba(255, 179, 71, 0.45);
217+
}
218+
219+
.feedback.reveal {
220+
background: rgba(255, 126, 121, 0.1);
221+
border: 1px solid rgba(255, 126, 121, 0.4);
222+
}
223+
224+
.award {
225+
font-weight: 700;
226+
color: var(--accent);
227+
}
228+
229+
.done {
230+
text-align: center;
231+
}
232+
233+
@keyframes rise {
234+
from {
235+
opacity: 0;
236+
transform: translateY(8px);
237+
}
238+
to {
239+
opacity: 1;
240+
transform: translateY(0);
241+
}
242+
}
243+
244+
@media (max-width: 860px) {
245+
.stats,
246+
.grid {
247+
grid-template-columns: 1fr;
248+
}
249+
250+
.actions {
251+
flex-direction: column;
252+
align-items: flex-start;
253+
}
254+
}

templates/base.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Code Quest Web</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com">
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
10+
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
11+
</head>
12+
<body>
13+
<div class="bg-shape bg-shape-a"></div>
14+
<div class="bg-shape bg-shape-b"></div>
15+
<main class="container">
16+
{% block content %}{% endblock %}
17+
</main>
18+
</body>
19+
</html>

templates/home.html

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{% extends "base.html" %}
2+
{% block content %}
3+
<section class="hero">
4+
<p class="eyebrow">Code Quest Web</p>
5+
<h1>Learn Python Like a Game</h1>
6+
<p class="lead">Built for absolute beginners. Every level teaches one concept, checks your answer, and explains why it works.</p>
7+
<div class="stats">
8+
<div class="card stat">
9+
<span>Score</span>
10+
<strong>{{ progress.score }}</strong>
11+
</div>
12+
<div class="card stat">
13+
<span>Lessons Complete</span>
14+
<strong>{{ progress.python_done }}/3</strong>
15+
</div>
16+
<div class="card stat">
17+
<span>Current Lesson</span>
18+
<strong>{{ current }}/{{ total }}</strong>
19+
</div>
20+
</div>
21+
<div class="actions">
22+
<a class="btn btn-primary" href="{{ url_for('python_lesson') }}">Start Learning</a>
23+
<form method="post" action="{{ url_for('reset') }}">
24+
<button class="btn btn-ghost" type="submit">Reset Progress</button>
25+
</form>
26+
</div>
27+
</section>
28+
29+
<section class="grid">
30+
<article class="card feature">
31+
<h2>1. Try</h2>
32+
<p>Answer a challenge using what you know right now.</p>
33+
</article>
34+
<article class="card feature">
35+
<h2>2. Learn</h2>
36+
<p>Get hints and beginner-safe breakdowns if you miss.</p>
37+
</article>
38+
<article class="card feature">
39+
<h2>3. Master</h2>
40+
<p>Earn points and build confidence one concept at a time.</p>
41+
</article>
42+
</section>
43+
{% endblock %}

0 commit comments

Comments
 (0)