first commit

This commit is contained in:
John Stephani 2026-03-16 16:25:34 -05:00
commit d52af8f024
6 changed files with 249 additions and 0 deletions

27
app/Dockerfile Executable file
View File

@ -0,0 +1,27 @@
# syntax=docker/dockerfile:1.4
FROM --platform=$BUILDPLATFORM python:3.10-alpine AS builder
WORKDIR /app
COPY requirements.txt /app
RUN --mount=type=cache,target=/root/.cache/pip \
pip3 install -r requirements.txt
COPY . /app
ENTRYPOINT ["python3"]
CMD ["app.py"]
FROM builder as dev-envs
RUN <<EOF
apk update
apk add git
EOF
RUN <<EOF
addgroup -S docker
adduser -S --shell /bin/bash --ingroup docker vscode
EOF
# install Docker tools (cli, buildx, compose)
COPY --from=gloursdocker/docker / /

13
app/app.py Executable file
View File

@ -0,0 +1,13 @@
from flask import Flask, send_file, render_template, request
import subprocess
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
subprocess.run(["python3", "make_db.py"])
app.run(host='0.0.0.0', port=8000)

3
app/requirements.txt Executable file
View File

@ -0,0 +1,3 @@
flask
requests
pygments

86
app/static/css/style.css Normal file
View File

@ -0,0 +1,86 @@
:root {
--lvl-1: #f9df70;
--lvl-2: #a0c35a;
--lvl-3: #b0c4ef;
--lvl-4: #ba81c5;
}
body {
font-family: sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background: #fff;
color: #333;
padding: 20px;
}
h1 {
text-transform: uppercase;
border-bottom: 2px solid #000;
margin-bottom: 20px;
letter-spacing: 2px;
}
#grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
width: 100%;
max-width: 500px;
margin-bottom: 20px;
}
.card {
aspect-ratio: 1/1;
background: #efefe6;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
cursor: pointer;
text-align: center;
padding: 5px;
user-select: none;
transition: transform 0.1s;
}
.card.selected {
background: #5a594e;
color: white;
}
.solved {
grid-column: span 4;
aspect-ratio: auto;
height: 65px;
display: flex;
flex-direction: column;
border-radius: 6px;
cursor: default;
}
.solved div {
font-size: 13px;
opacity: 0.9;
margin-top: 4px;
font-weight: normal;
}
.controls {
display: flex;
gap: 12px;
}
button {
padding: 12px 24px;
border-radius: 25px;
border: 1px solid #000;
background: white;
cursor: pointer;
font-weight: bold;
font-size: 14px;
}
button:disabled {
opacity: 0.3;
cursor: not-allowed;
}
#message {
margin-top: 20px;
font-weight: bold;
height: 24px;
color: #d33;
}

111
app/templates/index.html Executable file
View File

@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connections, but better</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<h1>Connections</h1>
<div id="grid"></div>
<div class="controls">
<button id="shuffleBtn">Shuffle</button>
<button id="deselectBtn">Deselect All</button>
<button id="submitBtn" disabled>Submit</button>
</div>
<div id="message"></div>
<script>
// EDIT YOUR CATEGORIES HERE
const categories = [
{name: "linux commands", items: ["cd", "touch", "cat", "find"], level: 1},
{name: "types of HTML input", items: ["radio", "password", "submit", "text"], level: 2},
{name: "sticky ___", items: ["tape", "fingers", "situation", "wicket"], level: 3},
{name: "how i feel, spelled backwards", items: ["live", "deliver", "desserts", "denier"], level: 4}
];
let words = categories.flatMap(cat => cat.items).sort(() => Math.random() - 0.5);
let selected = [];
let solvedCategories = [];
const grid = document.getElementById('grid');
const submitBtn = document.getElementById('submitBtn');
const message = document.getElementById('message');
function render() {
grid.innerHTML = '';
// 1. Render rows for already solved categories
solvedCategories.forEach(cat => {
const row = document.createElement('div');
row.className = 'card solved';
row.style.backgroundColor = `var(--lvl-${cat.level})`;
row.innerHTML = `<strong>${cat.name}</strong><div>${cat.items.join(', ')}</div>`;
grid.appendChild(row);
});
// 2. Render remaining active word cards
words.forEach(word => {
const card = document.createElement('div');
card.className = 'card' + (selected.includes(word) ? ' selected' : '');
card.textContent = word;
card.onclick = () => toggleSelect(word);
grid.appendChild(card);
});
submitBtn.disabled = selected.length !== 4;
}
function toggleSelect(word) {
if (selected.includes(word)) {
selected = selected.filter(w => w !== word);
} else if (selected.length < 4) {
selected.push(word);
}
render();
}
document.getElementById('deselectBtn').onclick = () => {
selected = [];
render();
};
document.getElementById('shuffleBtn').onclick = () => {
words.sort(() => Math.random() - 0.5);
render();
};
document.getElementById('submitBtn').onclick = () => {
const match = categories.find(cat =>
selected.every(word => cat.items.includes(word))
);
if (match) {
solvedCategories.push(match);
words = words.filter(w => !selected.includes(w));
selected = [];
if (solvedCategories.length === 4) {
message.style.color = "green";
message.textContent = "Perfect!";
}
} else {
message.textContent = "Not a set!";
setTimeout(() => message.textContent = "", 2000);
}
render();
};
words.sort(() => Math.random() - 0.5);
words.sort(() => Math.random() - 0.5);
words.sort(() => Math.random() - 0.5);
render();
</script>
</body>
</html>

9
compose.yaml Executable file
View File

@ -0,0 +1,9 @@
services:
web:
build:
context: app
target: builder
restart: unless-stopped
stop_signal: SIGINT
ports:
- '8003:8000'