Compare commits

..
9 Commits
Author SHA1 Message Date
Jiří Stachandaider 66668b3209 feat: add minimal, dark, and editorial theme variants
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 23:21:10 +02:00
Jiří Stachandaider 1d548b434b feat: redesign frontend UI with modern styling
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 23:18:18 +02:00
Jiří Stach 05f6b6386d Adding to gitignoe 2026-10-09 23:14:35 +02:00
Jiří Stachandaider ebf52f3443 fix: reload round data when navigating holes
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 23:08:26 +02:00
Jiří Stachandaider 74769f057f feat: add draft auto-save and remove save button
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 23:02:06 +02:00
Jiří Stachandaider ad0032bea1 feat: add tee selection and skip hole button
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 22:59:15 +02:00
Jiří Stachandaider 935f5bd85b fix: delete stale golf.db to match new schema
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 22:54:09 +02:00
Jiří Stachandaider d7c491ec22 feat: refine scoring with hole info, putts, penalties, bunkers
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 22:52:46 +02:00
Jiří Stachandaider 9f72bf3a6a fix: add expire_on_commit=False to sessionmaker
Co-authored-by: aider (openrouter/thinkingmachines/inkling-small:free) <aider@aider.chat>
2026-10-09 22:11:08 +02:00
7 changed files with 719 additions and 119 deletions

No files matched your search

+2
View File
@@ -1 +1,3 @@
.aider*
backend/__pycache__/
backend/golf.db
+86 -54
View File
@@ -7,7 +7,7 @@ import os
DATABASE_URL = "sqlite:///./backend/golf.db"
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, expire_on_commit=False)
Base = declarative_base()
class User(Base):
@@ -28,6 +28,8 @@ class Hole(Base):
number = Column(Integer, nullable=False)
par = Column(Integer, nullable=False)
yardage = Column(Integer, nullable=False)
yardage_meters = Column(Integer, default=0)
tee = Column(String, default="White")
class Round(Base):
__tablename__ = "rounds"
@@ -35,6 +37,7 @@ class Round(Base):
user_id = Column(Integer, ForeignKey("users.id"))
course_id = Column(Integer, ForeignKey("courses.id"))
status = Column(String, default="in_progress")
tee = Column(String, default="White")
class HoleScore(Base):
__tablename__ = "hole_scores"
@@ -46,6 +49,14 @@ class HoleScore(Base):
completed = Column(Boolean, default=False)
fairway_hit = Column(Boolean, default=False)
gir = Column(Boolean, default=False)
putts_green = Column(Integer, default=0)
putts_off_green = Column(Integer, default=0)
penalty_water = Column(Boolean, default=False)
penalty_ob = Column(Boolean, default=False)
penalty_lost = Column(Boolean, default=False)
bunker_greenside = Column(Boolean, default=False)
bunker_fairway = Column(Boolean, default=False)
unfinished = Column(Boolean, default=False)
class Putt(Base):
__tablename__ = "putts"
@@ -72,6 +83,9 @@ HoleScore.round = relationship("Round", back_populates="hole_scores")
HoleScore.putts = relationship("Putt", back_populates="hole_score")
Putt.hole_score = relationship("HoleScore", back_populates="putts")
if os.path.exists("backend/golf.db"):
os.remove("backend/golf.db")
Base.metadata.create_all(bind=engine)
app = FastAPI()
@@ -86,15 +100,15 @@ def init_mock():
db.commit()
db.refresh(course)
holes = [
Hole(course_id=course.id, number=1, par=4, yardage=350),
Hole(course_id=course.id, number=2, par=3, yardage=180),
Hole(course_id=course.id, number=3, par=5, yardage=520),
Hole(course_id=course.id, number=4, par=4, yardage=390),
Hole(course_id=course.id, number=5, par=3, yardage=160),
Hole(course_id=course.id, number=6, par=4, yardage=410),
Hole(course_id=course.id, number=7, par=5, yardage=540),
Hole(course_id=course.id, number=8, par=4, yardage=370),
Hole(course_id=course.id, number=9, par=3, yardage=190),
Hole(course_id=course.id, number=1, par=4, yardage=350, yardage_meters=320, tee="White"),
Hole(course_id=course.id, number=2, par=3, yardage=180, yardage_meters=165, tee="White"),
Hole(course_id=course.id, number=3, par=5, yardage=520, yardage_meters=480, tee="Yellow"),
Hole(course_id=course.id, number=4, par=4, yardage=390, yardage_meters=360, tee="White"),
Hole(course_id=course.id, number=5, par=3, yardage=160, yardage_meters=145, tee="Red"),
Hole(course_id=course.id, number=6, par=4, yardage=410, yardage_meters=375, tee="Blue"),
Hole(course_id=course.id, number=7, par=5, yardage=540, yardage_meters=495, tee="Yellow"),
Hole(course_id=course.id, number=8, par=4, yardage=370, yardage_meters=340, tee="White"),
Hole(course_id=course.id, number=9, par=3, yardage=190, yardage_meters=175, tee="Red"),
]
db.add_all(holes)
db.commit()
@@ -119,21 +133,22 @@ def signup(req: SignupRequest):
class StartRoundRequest(BaseModel):
user_id: int
course_id: int
tee: str = "White"
@app.post("/round")
def start_round(req: StartRoundRequest):
db = SessionLocal()
round_obj = Round(user_id=req.user_id, course_id=req.course_id, status="in_progress")
round_obj = Round(user_id=req.user_id, course_id=req.course_id, status="in_progress", tee=req.tee)
db.add(round_obj)
db.commit()
db.refresh(round_obj)
holes = db.query(Hole).filter(Hole.course_id == req.course_id).all()
for h in holes:
hs = HoleScore(round_id=round_obj.id, hole_number=h.number, strokes=0, skipped=False, completed=False, fairway_hit=False, gir=False)
hs = HoleScore(round_id=round_obj.id, hole_number=h.number, strokes=0, skipped=False, completed=False, fairway_hit=False, gir=False, putts_green=0, putts_off_green=0, unfinished=False)
db.add(hs)
db.commit()
db.close()
return {"id": round_obj.id, "status": round_obj.status}
return {"id": round_obj.id, "status": round_obj.status, "tee": round_obj.tee}
@app.get("/course")
def get_course():
@@ -144,7 +159,7 @@ def get_course():
return {
"id": course.id,
"name": course.name,
"holes": [{"number": h.number, "par": h.par, "yardage": h.yardage} for h in holes]
"holes": [{"number": h.number, "par": h.par, "yardage": h.yardage, "yardage_meters": h.yardage_meters, "tee": h.tee} for h in holes]
}
@app.get("/round/{round_id}")
@@ -160,12 +175,11 @@ def get_round(round_id: int):
"user_id": round_obj.user_id,
"course_id": round_obj.course_id,
"status": round_obj.status,
"tee": round_obj.tee,
"holes": []
}
for hs in hole_scores:
putts = db.query(Putt).filter(Putt.hole_score_id == hs.id).all()
penalties = db.query(Penalty).filter(Penalty.hole_score_id == hs.id).all()
bunkers = db.query(Bunker).filter(Bunker.hole_score_id == hs.id).all()
hole = db.query(Hole).filter(Hole.course_id == round_obj.course_id, Hole.number == hs.hole_number).first()
result["holes"].append({
"hole_score_id": hs.id,
"hole_number": hs.hole_number,
@@ -174,21 +188,36 @@ def get_round(round_id: int):
"completed": hs.completed,
"fairway_hit": hs.fairway_hit,
"gir": hs.gir,
"putts": [{"location": p.location} for p in putts],
"penalties": [{"type": p.type} for p in penalties],
"bunkers": [{"location": b.location} for b in bunkers]
"unfinished": hs.unfinished,
"putts_green": hs.putts_green,
"putts_off_green": hs.putts_off_green,
"penalty_water": hs.penalty_water,
"penalty_ob": hs.penalty_ob,
"penalty_lost": hs.penalty_lost,
"bunker_greenside": hs.bunker_greenside,
"bunker_fairway": hs.bunker_fairway,
"hole_info": {
"par": hole.par if hole else None,
"yardage_meters": hole.yardage_meters if hole else 0,
"tee": round_obj.tee
}
})
db.close()
return result
class UpdateHoleRequest(BaseModel):
strokes: int = 0
strokes: int = 1
skipped: bool = False
completed: bool = False
fairway_hit: bool = False
putts: list = []
penalties: list = []
bunkers: list = []
unfinished: bool = False
putts_green: int = 0
putts_off_green: int = 0
penalty_water: bool = False
penalty_ob: bool = False
penalty_lost: bool = False
bunker_greenside: bool = False
bunker_fairway: bool = False
@app.post("/hole/{hole_score_id}")
def update_hole(hole_score_id: int, req: UpdateHoleRequest):
@@ -201,55 +230,58 @@ def update_hole(hole_score_id: int, req: UpdateHoleRequest):
hs.skipped = req.skipped
hs.completed = req.completed
hs.fairway_hit = req.fairway_hit
hs.unfinished = req.unfinished
hs.putts_green = req.putts_green
hs.putts_off_green = req.putts_off_green
hs.penalty_water = req.penalty_water
hs.penalty_ob = req.penalty_ob
hs.penalty_lost = req.penalty_lost
hs.bunker_greenside = req.bunker_greenside
hs.bunker_fairway = req.bunker_fairway
db.query(Putt).filter(Putt.hole_score_id == hole_score_id).delete()
db.query(Penalty).filter(Penalty.hole_score_id == hole_score_id).delete()
db.query(Bunker).filter(Bunker.hole_score_id == hole_score_id).delete()
for p in req.putts:
db.add(Putt(hole_score_id=hole_score_id, location=p.get("location", "green")))
for pen in req.penalties:
db.add(Penalty(hole_score_id=hole_score_id, type=pen.get("type", "water")))
for b in req.bunkers:
db.add(Bunker(hole_score_id=hole_score_id, location=b.get("location", "greenside")))
putts = db.query(Putt).filter(Putt.hole_score_id == hole_score_id).all()
putts_count = len(putts)
round_obj = db.query(Round).filter(Round.id == hs.round_id).first()
hole = db.query(Hole).filter(Hole.course_id == round_obj.course_id, Hole.number == hs.hole_number).first()
if hole:
all_green = all(p.location == "green" for p in putts) if putts else True
hs.gir = (hs.strokes - putts_count) <= (hole.par - 2) and all_green
total_putts = hs.putts_green + hs.putts_off_green
all_green = hs.putts_off_green == 0
hs.gir = (not hs.unfinished) and (hs.strokes - total_putts) <= (hole.par - 2) and all_green
db.commit()
db.refresh(hs)
db.close()
return {"hole_score_id": hs.id, "gir": hs.gir, "strokes": hs.strokes, "putts": putts_count}
return {
"hole_score_id": hs.id,
"gir": hs.gir,
"strokes": hs.strokes,
"putts_green": hs.putts_green,
"putts_off_green": hs.putts_off_green,
"unfinished": hs.unfinished
}
@app.get("/stats/{round_id}")
def get_stats(round_id: int):
db = SessionLocal()
hs_list = db.query(HoleScore).filter(HoleScore.round_id == round_id).all()
total_strokes = sum(hs.strokes for hs in hs_list if not hs.skipped)
total_putts = 0
fairways = 0
girs = 0
for hs in hs_list:
if hs.skipped:
continue
putts = db.query(Putt).filter(Putt.hole_score_id == hs.id).all()
total_putts += len(putts)
if hs.fairway_hit:
fairways += 1
if hs.gir:
girs += 1
total_strokes = sum(hs.strokes for hs in hs_list if not hs.skipped and not hs.unfinished)
total_putts = sum((hs.putts_green + hs.putts_off_green) for hs in hs_list if not hs.skipped and not hs.unfinished)
fairways = sum(1 for hs in hs_list if hs.fairway_hit and not hs.skipped and not hs.unfinished)
girs = sum(1 for hs in hs_list if hs.gir and not hs.skipped and not hs.unfinished)
penalties = sum(
(1 if hs.penalty_water else 0) + (1 if hs.penalty_ob else 0) + (1 if hs.penalty_lost else 0)
for hs in hs_list if not hs.skipped and not hs.unfinished
)
bunker_greenside = sum(1 for hs in hs_list if hs.bunker_greenside and not hs.skipped and not hs.unfinished)
bunker_fairway = sum(1 for hs in hs_list if hs.bunker_fairway and not hs.skipped and not hs.unfinished)
db.close()
return {
"total_strokes": total_strokes,
"total_putts": total_putts,
"fairways_hit": fairways,
"gir": girs,
"holes_played": len([h for h in hs_list if not h.skipped])
"penalty_strokes": penalties,
"bunker_greenside": bunker_greenside,
"bunker_fairway": bunker_fairway,
"holes_played": len([h for h in hs_list if not h.skipped and not h.unfinished])
}
app.mount("/", StaticFiles(directory="frontend", html=True), name="static")
+100 -32
View File
@@ -26,10 +26,11 @@ async function loadCourse() {
async function startRound() {
const res = await fetch('/course');
const course = await res.json();
const tee = document.getElementById('tee-select').value;
const res2 = await fetch('/round', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({user_id: currentUser.id, course_id: course.id})
body: JSON.stringify({user_id: currentUser.id, course_id: course.id, tee: tee})
});
currentRound = await res2.json();
document.getElementById('start-section').style.display = 'none';
@@ -49,23 +50,64 @@ async function loadRound() {
loadStats();
}
function saveDraft() {
const id = document.getElementById('hole-score-id').value;
if (!id) return;
const draft = {
strokes: document.getElementById('strokes').value,
unfinished: document.getElementById('unfinished').checked,
fairway: document.getElementById('fairway').checked,
putts_green: document.getElementById('putts-green').value,
putts_off_green: document.getElementById('putts-off-green').value,
pen_water: document.getElementById('pen-water').checked,
pen_ob: document.getElementById('pen-ob').checked,
pen_lost: document.getElementById('pen-lost').checked,
bunker_greenside: document.getElementById('bunker-greenside').checked,
bunker_fairway: document.getElementById('bunker-fairway').checked
};
localStorage.setItem('draft_' + id, JSON.stringify(draft));
}
function applyDraft(hole) {
const draftRaw = localStorage.getItem('draft_' + hole.hole_score_id);
if (!draftRaw) return;
const d = JSON.parse(draftRaw);
document.getElementById('strokes').value = d.strokes !== undefined ? d.strokes : (hole.strokes || 1);
document.getElementById('unfinished').checked = d.unfinished || false;
document.getElementById('fairway').checked = d.fairway || false;
document.getElementById('putts-green').value = d.putts_green !== undefined ? d.putts_green : (hole.putts_green || 0);
document.getElementById('putts-off-green').value = d.putts_off_green !== undefined ? d.putts_off_green : (hole.putts_off_green || 0);
document.getElementById('pen-water').checked = d.pen_water || false;
document.getElementById('pen-ob').checked = d.pen_ob || false;
document.getElementById('pen-lost').checked = d.pen_lost || false;
document.getElementById('bunker-greenside').checked = d.bunker_greenside || false;
document.getElementById('bunker-fairway').checked = d.bunker_fairway || false;
}
function renderHole(hole) {
document.getElementById('hole-score-id').value = hole.hole_score_id;
document.getElementById('hole-label').textContent = `Hole ${hole.hole_number}`;
document.getElementById('strokes').value = hole.strokes || 0;
document.getElementById('fairway').checked = hole.fairway_hit;
document.getElementById('skipped').checked = hole.skipped;
document.getElementById('completed').checked = hole.completed;
document.getElementById('strokes').value = hole.strokes || 1;
document.getElementById('unfinished').checked = hole.unfinished || false;
document.getElementById('fairway').checked = hole.fairway_hit || false;
document.getElementById('gir-display').textContent = hole.gir ? 'Yes' : 'No';
const putts = hole.putts || [];
document.getElementById('putts-input').value = putts.map(p => p.location).join(',');
document.getElementById('putts-green').value = hole.putts_green || 0;
document.getElementById('putts-off-green').value = hole.putts_off_green || 0;
const pens = (hole.penalties || []).map(p => p.type).join(',');
document.getElementById('penalties-input').value = pens;
document.getElementById('pen-water').checked = hole.penalty_water || false;
document.getElementById('pen-ob').checked = hole.penalty_ob || false;
document.getElementById('pen-lost').checked = hole.penalty_lost || false;
const buns = (hole.bunkers || []).map(b => b.location).join(',');
document.getElementById('bunkers-input').value = buns;
document.getElementById('bunker-greenside').checked = hole.bunker_greenside || false;
document.getElementById('bunker-fairway').checked = hole.bunker_fairway || false;
applyDraft(hole);
const info = hole.hole_info || {};
document.getElementById('hole-info').innerHTML = `
<strong>Par ${info.par}</strong> | Tee: ${info.tee} | ${info.yardage_meters}m
`;
}
function renderNav(holes) {
@@ -75,49 +117,72 @@ function renderNav(holes) {
function prevHole() {
if (currentIndex > 0) {
currentIndex--;
const holes = currentData.holes.sort((a,b) => a.hole_number - b.hole_number);
renderHole(holes[currentIndex]);
renderNav(holes);
saveHole().then(() => {
currentIndex--;
loadRound();
});
}
}
function nextHole() {
const holes = currentData.holes.sort((a,b) => a.hole_number - b.hole_number);
if (currentIndex < holes.length - 1) {
saveHole().then(() => {
currentIndex++;
renderHole(holes[currentIndex]);
renderNav(holes);
}
loadRound();
});
}
async function saveHole() {
const id = document.getElementById('hole-score-id').value;
const strokes = parseInt(document.getElementById('strokes').value) || 0;
if (!id) return Promise.resolve();
const strokes = parseInt(document.getElementById('strokes').value) || 1;
const unfinished = document.getElementById('unfinished').checked;
const fairway = document.getElementById('fairway').checked;
const skipped = document.getElementById('skipped').checked;
const completed = document.getElementById('completed').checked;
const puttsStr = document.getElementById('putts-input').value;
const putts = puttsStr.split(',').filter(s => s.trim()).map(s => ({location: s.trim()}));
const puttsGreen = parseInt(document.getElementById('putts-green').value) || 0;
const puttsOffGreen = parseInt(document.getElementById('putts-off-green').value) || 0;
const pensStr = document.getElementById('penalties-input').value;
const penalties = pensStr.split(',').filter(s => s.trim()).map(s => ({type: s.trim()}));
const penWater = document.getElementById('pen-water').checked;
const penOb = document.getElementById('pen-ob').checked;
const penLost = document.getElementById('pen-lost').checked;
const bunsStr = document.getElementById('bunkers-input').value;
const bunkers = bunsStr.split(',').filter(s => s.trim()).map(s => ({location: s.trim()}));
const bunkerGreenside = document.getElementById('bunker-greenside').checked;
const bunkerFairway = document.getElementById('bunker-fairway').checked;
const res = await fetch(`/hole/${id}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
strokes, skipped, completed, fairway_hit: fairway,
putts, penalties, bunkers
strokes, skipped: false, completed: false, fairway_hit: fairway, unfinished,
putts_green: puttsGreen, putts_off_green: puttsOffGreen,
penalty_water: penWater, penalty_ob: penOb, penalty_lost: penLost,
bunker_greenside: bunkerGreenside, bunker_fairway: bunkerFairway
})
});
const result = await res.json();
document.getElementById('gir-display').textContent = result.gir ? 'Yes' : 'No';
loadRound();
localStorage.removeItem('draft_' + id);
loadStats();
return result;
}
async function skipHole() {
const id = document.getElementById('hole-score-id').value;
await fetch(`/hole/${id}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
strokes: 0, skipped: true, completed: false, fairway_hit: false, unfinished: false,
putts_green: 0, putts_off_green: 0,
penalty_water: false, penalty_ob: false, penalty_lost: false,
bunker_greenside: false, bunker_fairway: false
})
});
localStorage.removeItem('draft_' + id);
const holes = currentData.holes.sort((a,b) => a.hole_number - b.hole_number);
if (currentIndex < holes.length - 1) {
currentIndex++;
loadRound();
}
}
async function loadStats() {
@@ -128,6 +193,9 @@ async function loadStats() {
<p><strong>Total Putts:</strong> ${stats.total_putts}</p>
<p><strong>Fairways Hit:</strong> ${stats.fairways_hit}</p>
<p><strong>GIR:</strong> ${stats.gir}</p>
<p><strong>Penalty Strokes:</strong> ${stats.penalty_strokes}</p>
<p><strong>Bunker Greenside:</strong> ${stats.bunker_greenside}</p>
<p><strong>Bunker Fairway:</strong> ${stats.bunker_fairway}</p>
<p><strong>Holes Played:</strong> ${stats.holes_played}</p>
`;
}
+124 -33
View File
@@ -2,59 +2,150 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>Golf Prototype</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Golf Score Card — Prototype</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 720px; margin: 2rem auto; padding: 0 1rem; color: #222; }
section { margin-bottom: 1.5rem; padding: 1rem; border: 1px solid #ddd; border-radius: 8px; background: #fafafa; }
h1 { font-size: 1.5rem; }
label { display: inline-block; margin: 0.25rem 0; }
input, select { padding: 0.25rem; }
button { padding: 0.4rem 0.8rem; margin-right: 0.5rem; cursor: pointer; }
#stats-display p { margin: 0.2rem 0; }
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&family=Playfair+Display:wght@700&display=swap');
:root { --bg:#f4f6f0; --surface:#fff; --ink:#1a2e1a; --ink-secondary:#556b55; --accent:#2d6a4f; --accent-light:#d8e8dc; --accent-dark:#1b4332; --border:#dde5d8; --shadow:0 10px 30px rgba(26,46,26,0.08); --radius:16px; }
*{box-sizing:border-box;}
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--ink);margin:0;padding:2rem 1rem;line-height:1.45;}
.container{max-width:720px;margin:0 auto;}
header{text-align:center;margin-bottom:2.5rem;}
header h1{font-family:'Playfair Display',Georgia,serif;font-size:2.6rem;margin:0 0 .25rem;color:var(--accent-dark);letter-spacing:-.02em;}
header p{margin:0;color:var(--ink-secondary);font-size:1.05rem;}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:1.5rem 1.75rem;margin-bottom:1.25rem;box-shadow:var(--shadow);transition:transform .15s ease,box-shadow .15s ease;}
.card:hover{box-shadow:0 14px 40px rgba(26,46,26,0.12);}
h2{font-size:1.15rem;margin:0 0 1rem;color:var(--accent-dark);font-weight:700;letter-spacing:-.01em;}
label{display:block;font-weight:600;font-size:.85rem;margin:.75rem 0 .35rem;color:var(--ink-secondary);text-transform:uppercase;letter-spacing:.04em;}
input[type="text"],input[type="number"],select{width:100%;padding:.65rem .75rem;border:1.5px solid var(--border);border-radius:10px;font-size:1rem;background:#fafcf8;color:var(--ink);transition:border-color .2s,box-shadow .2s;}
input:focus,select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(45,106,79,0.12);}
button{display:inline-flex;align-items:center;gap:.35rem;padding:.65rem 1.1rem;border:none;border-radius:10px;font-weight:600;font-size:.95rem;cursor:pointer;transition:background .2s,transform .1s;}
button:active{transform:translateY(1px);}
.btn-primary{background:var(--accent);color:#fff;}
.btn-primary:hover{background:var(--accent-dark);}
.btn-secondary{background:#e8ede8;color:var(--ink);}
.btn-secondary:hover{background:#dde5d8;}
.btn-danger{background:#c0392b;color:#fff;}
.btn-danger:hover{background:#922b21;}
.row{display:flex;gap:.75rem;flex-wrap:wrap;align-items:center;}
.row>*{flex:1 1 auto;}
.hole-nav{display:flex;align-items:center;gap:.75rem;margin-bottom:1rem;}
.hole-nav strong{font-family:'Playfair Display',Georgia,serif;font-size:1.3rem;color:var(--accent-dark);min-width:6rem;text-align:center;}
.hole-info{background:var(--accent-light);color:var(--accent-dark);padding:.6rem .9rem;border-radius:10px;font-size:.9rem;font-weight:500;margin-bottom:1rem;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:.75rem 1rem;}
.form-grid label{margin-top:.25rem;}
.form-grid input[type="number"]{width:100%;}
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem;}
.stat-card{background:var(--bg);border:1px solid var(--border);border-radius:12px;padding:1rem;text-align:center;}
.stat-card .label{font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;color:var(--ink-secondary);font-weight:600;}
.stat-card .value{font-family:'Playfair Display',Georgia,serif;font-size:1.6rem;color:var(--accent-dark);margin-top:.25rem;}
.gir-badge{display:inline-block;padding:.25rem .6rem;border-radius:999px;font-weight:700;font-size:.85rem;background:var(--accent-light);color:var(--accent-dark);}
.theme-switch{display:flex;gap:.5rem;justify-content:center;margin-top:1rem;}
.theme-switch a{padding:.35rem .7rem;border-radius:8px;background:#e8ede8;color:var(--ink);text-decoration:none;font-weight:600;font-size:.85rem;}
.theme-switch a:hover{background:#dde5d8;}
@media(max-width:640px){.form-grid{grid-template-columns:1fr;}header h1{font-size:2rem;}}
</style>
</head>
<body>
<h1>Golf Score Card — Prototype</h1>
<div class="container">
<header>
<h1>Golf Score Card</h1>
<p>Track your round with precision</p>
</header>
<section id="signup-section">
<section id="signup-section" class="card">
<h2>Sign Up</h2>
<label>Name: <input id="name" type="text" placeholder="Your name" /></label><br>
<label>Handicap: <input id="hcp" type="number" value="0" /></label><br>
<button onclick="signup()">Sign Up</button>
<label for="name">Name</label>
<input id="name" type="text" placeholder="Your name" />
<label for="hcp">Handicap</label>
<input id="hcp" type="number" value="0" />
<button class="btn-primary" onclick="signup()">Sign Up</button>
</section>
<section id="start-section" style="display:none;">
<h2>Course</h2>
<p id="course-info"></p>
<button onclick="startRound()">Start Round</button>
<section id="start-section" class="card" style="display:none;">
<h2>Start Round</h2>
<p id="course-info" style="margin:0 0 1rem; color: var(--ink-secondary);"></p>
<label for="tee-select">Tee Box</label>
<select id="tee-select">
<option>White</option>
<option>Yellow</option>
<option>Blue</option>
<option>Red</option>
</select>
<button class="btn-primary" onclick="startRound()" style="margin-top:1rem;">Start Round</button>
</section>
<section id="round-section" style="display:none;">
<section id="round-section" class="card" style="display:none;">
<h2>Round</h2>
<div>
<button id="prev-btn" onclick="prevHole()">Prev</button>
<div class="hole-nav">
<button class="btn-secondary" id="prev-btn" onclick="prevHole()">Prev</button>
<strong id="hole-label">Hole 1</strong>
<button id="next-btn" onclick="nextHole()">Next</button>
<button class="btn-secondary" id="next-btn" onclick="nextHole()">Next</button>
<button class="btn-danger" onclick="skipHole()">Skip</button>
</div>
<form id="hole-form" onsubmit="event.preventDefault(); saveHole();">
<div id="hole-info" class="hole-info"></div>
<form id="hole-form" onsubmit="event.preventDefault();">
<input type="hidden" id="hole-score-id" />
<label>Strokes: <input id="strokes" type="number" value="0" min="0" /></label><br>
<label>Fairway Hit: <input id="fairway" type="checkbox" /></label><br>
<label>Skipped: <input id="skipped" type="checkbox" /></label><br>
<label>Completed: <input id="completed" type="checkbox" /></label><br>
<label>Putts (comma list): <input id="putts-input" type="text" placeholder="green,green" /></label><br>
<label>Penalties (comma list): <input id="penalties-input" type="text" placeholder="water" /></label><br>
<label>Bunkers (comma list): <input id="bunkers-input" type="text" placeholder="greenside" /></label><br>
<button type="submit">Save Hole</button>
<div class="form-grid">
<div>
<label for="strokes">Strokes (1+)</label>
<input id="strokes" type="number" value="1" min="1" oninput="saveDraft()" />
</div>
<div>
<label for="unfinished">Unfinished (X)</label>
<input id="unfinished" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="fairway">Fairway Hit</label>
<input id="fairway" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="putts-green">Putts on Green</label>
<input id="putts-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="putts-off-green">Putts Off Green</label>
<input id="putts-off-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="pen-water">Penalty — Water</label>
<input id="pen-water" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-ob">Penalty — OB</label>
<input id="pen-ob" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-lost">Penalty — Lost Ball</label>
<input id="pen-lost" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-greenside">Bunker — Greenside</label>
<input id="bunker-greenside" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-fairway">Bunker — Fairway</label>
<input id="bunker-fairway" type="checkbox" oninput="saveDraft()" />
</div>
</div>
</form>
<p>GIR: <strong id="gir-display">No</strong></p>
<div style="margin-top:1rem;">
<span class="gir-badge">GIR: <strong id="gir-display">No</strong></span>
</div>
</section>
<section id="stats-section" style="display:none;">
<section id="stats-section" class="card" style="display:none;">
<h2>Basic Stats</h2>
<div id="stats-display"></div>
<div id="stats-display" class="stats-grid"></div>
</section>
<div class="theme-switch">
<a href="theme-minimal.html">Minimal</a>
<a href="theme-dark.html">Dark</a>
<a href="theme-editorial.html">Editorial</a>
</div>
</div>
<script src="app.js"></script>
</body>
</html>
+139
View File
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Golf Score Card — Dark</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;500&family=Inter:wght@300;600&display=swap');
:root{--bg:#0b0f14;--surface:#121620;--ink:#e8e8e8;--ink-secondary:#8892a0;--accent:#c8f560;--accent-dark:#a3c44a;--border:#1f2530;--shadow:0 10px 30px rgba(0,0,0,0.4);}
*{box-sizing:border-box;}
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--ink);margin:0;padding:2rem 1rem;line-height:1.4;}
.container{max-width:720px;margin:0 auto;}
header{text-align:center;margin-bottom:2.5rem;}
header h1{font-family:'JetBrains Mono',monospace;font-size:2.2rem;font-weight:300;color:var(--accent);letter-spacing:-.03em;margin:0 0 .25rem;}
header p{margin:0;color:var(--ink-secondary);font-size:.95rem;}
.card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:1.5rem 1.75rem;margin-bottom:1.25rem;box-shadow:var(--shadow);}
h2{font-size:.9rem;text-transform:uppercase;letter-spacing:.08em;color:var(--accent);font-weight:600;margin:0 0 1rem;}
label{display:block;font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-secondary);margin:.75rem 0 .25rem;font-weight:500;}
input,select{width:100%;padding:.65rem .75rem;border:1px solid var(--border);border-radius:8px;background:#0f1318;color:var(--ink);font-family:inherit;font-size:.95rem;outline:none;}
input:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(200,245,96,0.15);}
button{display:inline-flex;align-items:center;gap:.35rem;padding:.6rem 1rem;border:1px solid var(--border);background:#1a1f2a;color:var(--ink);font-family:inherit;font-weight:600;font-size:.85rem;border-radius:8px;cursor:pointer;transition:background .2s;}
button:hover{background:#22283a;}
button.accent{background:var(--accent);color:#0b0f14;border-color:var(--accent);font-weight:700;}
button.accent:hover{background:var(--accent-dark);}
button.danger{border-color:#ff6b6b;color:#ff6b6b;}
button.danger:hover{background:#ff6b6b;color:#0b0f14;}
.hole-nav{display:flex;align-items:center;gap:.75rem;margin-bottom:1rem;}
.hole-nav strong{font-family:'JetBrains Mono',monospace;font-size:1.2rem;color:var(--accent);min-width:6rem;text-align:center;}
.hole-info{font-family:'JetBrains Mono',monospace;font-size:.85rem;color:var(--ink-secondary);margin-bottom:1rem;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:.75rem 1rem;}
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.75rem;}
.stat-card{background:#0f1318;border:1px solid var(--border);border-radius:10px;padding:1rem;text-align:center;}
.stat-card .label{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-secondary);font-weight:600;}
.stat-card .value{font-family:'JetBrains Mono',monospace;font-size:1.5rem;color:var(--accent);margin-top:.25rem;}
.gir-badge{display:inline-block;padding:.25rem .6rem;border-radius:999px;font-weight:700;font-size:.8rem;background:#1a1f2a;color:var(--accent);border:1px solid var(--border);}
.theme-switch{display:flex;gap:.5rem;justify-content:center;margin-top:1rem;}
.theme-switch a{padding:.35rem .7rem;border-radius:8px;background:#1a1f2a;color:var(--ink-secondary);text-decoration:none;font-weight:600;font-size:.85rem;border:1px solid var(--border);}
.theme-switch a:hover{color:var(--ink);border-color:var(--accent);}
@media(max-width:640px){.form-grid{grid-template-columns:1fr;}}
</style>
</head>
<body>
<div class="container">
<header>
<h1>GOLF // SCORE</h1>
<p>Dark mode tracking</p>
</header>
<section id="signup-section" class="card">
<h2>Sign Up</h2>
<label for="name">Name</label>
<input id="name" type="text" placeholder="Your name" />
<label for="hcp">Handicap</label>
<input id="hcp" type="number" value="0" />
<button class="accent" onclick="signup()">Enter</button>
</section>
<section id="start-section" class="card" style="display:none;">
<h2>Start Round</h2>
<p id="course-info" style="margin:0 0 1rem; color: var(--ink-secondary);"></p>
<label for="tee-select">Tee Box</label>
<select id="tee-select"><option>White</option><option>Yellow</option><option>Blue</option><option>Red</option></select>
<button class="accent" onclick="startRound()" style="margin-top:1rem;">Launch</button>
</section>
<section id="round-section" class="card" style="display:none;">
<h2>Round</h2>
<div class="hole-nav">
<button onclick="prevHole()">Prev</button>
<strong id="hole-label">Hole 1</strong>
<button onclick="nextHole()">Next</button>
<button class="danger" onclick="skipHole()">Skip</button>
</div>
<div id="hole-info" class="hole-info"></div>
<form id="hole-form" onsubmit="event.preventDefault();">
<input type="hidden" id="hole-score-id" />
<div class="form-grid">
<div>
<label for="strokes">Strokes</label>
<input id="strokes" type="number" value="1" min="1" oninput="saveDraft()" />
</div>
<div>
<label for="unfinished">Unfinished (X)</label>
<input id="unfinished" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="fairway">Fairway</label>
<input id="fairway" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="putts-green">Putts Green</label>
<input id="putts-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="putts-off-green">Putts Off</label>
<input id="putts-off-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="pen-water">Water</label>
<input id="pen-water" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-ob">OB</label>
<input id="pen-ob" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-lost">Lost</label>
<input id="pen-lost" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-greenside">Greenside</label>
<input id="bunker-greenside" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-fairway">Fairway Bunker</label>
<input id="bunker-fairway" type="checkbox" oninput="saveDraft()" />
</div>
</div>
</form>
<div style="margin-top:1rem;">
<span class="gir-badge">GIR: <strong id="gir-display">No</strong></span>
</div>
</section>
<section id="stats-section" class="card" style="display:none;">
<h2>Stats</h2>
<div id="stats-display" class="stats-grid"></div>
</section>
<div class="theme-switch">
<a href="index.html">Forest</a>
<a href="theme-minimal.html">Minimal</a>
<a href="theme-editorial.html">Editorial</a>
</div>
</div>
<script src="app.js"></script>
</body>
</html>
+137
View File
@@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Golf Score Card — Editorial</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,600;1,400&family=Inter:wght@300;400;600&display=swap');
:root{--bg:#f7f5f0;--ink:#2a2a2a;--ink-secondary:#6e6e6e;--accent:#8b5e3c;--accent-light:#efe8dc;--border:#e2ddd4;--shadow:0 4px 20px rgba(42,42,42,0.06);}
*{box-sizing:border-box;}
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--ink);margin:0;padding:3rem 1rem;line-height:1.5;}
.container{max-width:720px;margin:0 auto;}
header{text-align:center;margin-bottom:3rem;}
header h1{font-family:'Cormorant Garamond',Georgia,serif;font-size:3rem;font-weight:300;color:var(--ink);letter-spacing:-.02em;margin:0 0 .25rem;}
header p{font-family:'Cormorant Garamond',Georgia,serif;font-style:italic;color:var(--ink-secondary);font-size:1.15rem;margin:0;}
.card{background:#fff;border:1px solid var(--border);border-radius:4px;padding:2rem 2.25rem;margin-bottom:1.5rem;box-shadow:var(--shadow);position:relative;}
.card::before{content:"";position:absolute;top:0;left:0;width:4px;height:100%;background:var(--accent);border-radius:4px 0 0 4px;}
h2{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.4rem;font-weight:600;color:var(--ink);margin:0 0 1.25rem;}
label{display:block;font-size:.75rem;text-transform:uppercase;letter-spacing:.1em;color:var(--ink-secondary);margin:1rem 0 .35rem;font-weight:600;}
input,select{width:100%;padding:.7rem .8rem;border:1px solid var(--border);border-radius:6px;background:#fdfcf9;font-family:inherit;font-size:.95rem;color:var(--ink);outline:none;}
input:focus,select:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(139,94,60,0.1);}
button{padding:.65rem 1.2rem;border:1px solid var(--ink);background:var(--ink);color:#fff;font-family:inherit;font-weight:600;font-size:.9rem;border-radius:4px;cursor:pointer;transition:background .2s;}
button:hover{background:#333;}
button.secondary{background:#fff;color:var(--ink);border-color:var(--border);}
button.accent{background:var(--accent);color:#fff;border-color:var(--accent);}
button.accent:hover{background:#6e4a2e;}
.hole-nav{display:flex;align-items:center;gap:1rem;margin-bottom:1.25rem;padding-bottom:1rem;border-bottom:1px solid var(--border);}
.hole-nav strong{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.5rem;color:var(--ink);min-width:6rem;text-align:center;}
.hole-info{font-family:'Cormorant Garamond',Georgia,serif;font-style:italic;color:var(--ink-secondary);font-size:1rem;margin-bottom:1.25rem;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem 1.5rem;}
.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem;}
.stat{border-top:2px solid var(--accent);padding-top:.75rem;}
.stat .label{font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:var(--ink-secondary);font-weight:600;}
.stat .value{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.8rem;color:var(--ink);margin-top:.25rem;}
.gir{font-family:'Cormorant Garamond',Georgia,serif;font-style:italic;font-size:1.1rem;color:var(--accent);margin-top:1rem;}
.theme-switch{display:flex;gap:.75rem;justify-content:center;margin-top:2rem;}
.theme-switch a{font-family:'Cormorant Garamond',Georgia,serif;font-size:1rem;color:var(--ink-secondary);text-decoration:none;border-bottom:1px solid var(--border);padding-bottom:.15rem;}
.theme-switch a:hover{color:var(--ink);border-color:var(--accent);}
@media(max-width:640px){.form-grid{grid-template-columns:1fr;}.stats{grid-template-columns:repeat(2,1fr);}}
</style>
</head>
<body>
<div class="container">
<header>
<h1>The Score Card</h1>
<p>A refined record of the round</p>
</header>
<section id="signup-section" class="card">
<h2>Sign Up</h2>
<label for="name">Name</label>
<input id="name" type="text" placeholder="Your name" />
<label for="hcp">Handicap</label>
<input id="hcp" type="number" value="0" />
<button onclick="signup()">Begin</button>
</section>
<section id="start-section" class="card" style="display:none;">
<h2>Start Round</h2>
<p id="course-info" style="margin:0 0 1rem; color: var(--ink-secondary); font-family:'Cormorant Garamond',Georgia,serif; font-style:italic;"></p>
<label for="tee-select">Tee Box</label>
<select id="tee-select"><option>White</option><option>Yellow</option><option>Blue</option><option>Red</option></select>
<button class="accent" onclick="startRound()" style="margin-top:1rem;">Commence</button>
</section>
<section id="round-section" class="card" style="display:none;">
<h2>Round</h2>
<div class="hole-nav">
<button class="secondary" onclick="prevHole()">Previous</button>
<strong id="hole-label">Hole 1</strong>
<button onclick="nextHole()">Next</button>
<button onclick="skipHole()" style="border-color:#c0392b;color:#c0392b;">Skip</button>
</div>
<div id="hole-info" class="hole-info"></div>
<form id="hole-form" onsubmit="event.preventDefault();">
<input type="hidden" id="hole-score-id" />
<div class="form-grid">
<div>
<label for="strokes">Strokes</label>
<input id="strokes" type="number" value="1" min="1" oninput="saveDraft()" />
</div>
<div>
<label for="unfinished">Unfinished (X)</label>
<input id="unfinished" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="fairway">Fairway</label>
<input id="fairway" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="putts-green">Putts on Green</label>
<input id="putts-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="putts-off-green">Putts Off Green</label>
<input id="putts-off-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="pen-water">Water</label>
<input id="pen-water" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-ob">OB</label>
<input id="pen-ob" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-lost">Lost Ball</label>
<input id="pen-lost" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-greenside">Greenside</label>
<input id="bunker-greenside" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-fairway">Fairway Bunker</label>
<input id="bunker-fairway" type="checkbox" oninput="saveDraft()" />
</div>
</div>
</form>
<div class="gir">GIR: <strong id="gir-display">No</strong></div>
</section>
<section id="stats-section" class="card" style="display:none;">
<h2>Summary</h2>
<div id="stats-display" class="stats"></div>
</section>
<div class="theme-switch">
<a href="index.html">Forest</a>
<a href="theme-minimal.html">Minimal</a>
<a href="theme-dark.html">Dark</a>
</div>
</div>
<script src="app.js"></script>
</body>
</html>
+131
View File
@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Golf Score Card — Minimal</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;500;700&display=swap');
:root{--bg:#faf9f6;--ink:#111;--line:#ddd;--accent:#bfa15f;}
*{box-sizing:border-box;}
body{font-family:'Space Grotesk',system-ui,sans-serif;background:var(--bg);color:var(--ink);margin:0;padding:3rem 1rem;line-height:1.35;}
.container{max-width:640px;margin:0 auto;}
h1{font-size:2.2rem;font-weight:700;letter-spacing:-.03em;margin:0 0 .25rem;}
.subtitle{color:#777;font-size:.95rem;margin-bottom:2.5rem;}
.card{border-top:1px solid var(--line);padding:1.5rem 0;margin-bottom:1.5rem;}
h2{font-size:.75rem;text-transform:uppercase;letter-spacing:.12em;color:#777;font-weight:700;margin:0 0 1rem;}
label{display:block;font-size:.7rem;text-transform:uppercase;letter-spacing:.08em;color:#777;margin:1rem 0 .25rem;font-weight:500;}
input,select{width:100%;padding:.6rem .5rem;border:none;border-bottom:1.5px solid var(--line);background:transparent;font-family:inherit;font-size:1rem;color:var(--ink);outline:none;}
input:focus,select:focus{border-bottom-color:var(--accent);}
button{padding:.55rem 1rem;border:1px solid var(--ink);background:var(--ink);color:#fff;font-family:inherit;font-weight:500;font-size:.85rem;border-radius:0;cursor:pointer;transition:background .2s;}
button:hover{background:#333;}
button.secondary{background:#fff;color:var(--ink);border-color:var(--line);}
button.danger{background:#fff;color:#c0392b;border-color:#c0392b;}
.hole-nav{display:flex;align-items:center;gap:.75rem;margin-bottom:1rem;}
.hole-nav strong{font-size:1.1rem;font-weight:700;}
.hole-info{font-size:.85rem;color:#555;margin-bottom:1rem;}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:.5rem 1rem;}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1rem;}
.stat{border-top:1px solid var(--line);padding-top:.75rem;}
.stat .label{font-size:.65rem;text-transform:uppercase;letter-spacing:.08em;color:#777;}
.stat .value{font-size:1.4rem;font-weight:700;}
.gir{font-size:.85rem;color:var(--accent);font-weight:700;}
.theme-switch{display:flex;gap:.5rem;margin-top:2rem;}
.theme-switch a{font-size:.75rem;color:#777;text-decoration:none;border-bottom:1px solid var(--line);padding-bottom:.1rem;}
@media(max-width:640px){.form-grid{grid-template-columns:1fr;}.stats{grid-template-columns:repeat(2,1fr);}}
</style>
</head>
<body>
<div class="container">
<h1>Score Card</h1>
<p class="subtitle">Minimal tracking</p>
<section id="signup-section" class="card">
<h2>Sign Up</h2>
<label for="name">Name</label>
<input id="name" type="text" placeholder="Your name" />
<label for="hcp">Handicap</label>
<input id="hcp" type="number" value="0" />
<button onclick="signup()">Continue</button>
</section>
<section id="start-section" class="card" style="display:none;">
<h2>Start Round</h2>
<p id="course-info" style="margin:0 0 1rem; color:#777;"></p>
<label for="tee-select">Tee</label>
<select id="tee-select"><option>White</option><option>Yellow</option><option>Blue</option><option>Red</option></select>
<button onclick="startRound()" style="margin-top:1rem;">Start</button>
</section>
<section id="round-section" class="card" style="display:none;">
<h2>Round</h2>
<div class="hole-nav">
<button class="secondary" onclick="prevHole()">Back</button>
<strong id="hole-label">Hole 1</strong>
<button onclick="nextHole()">Next</button>
<button class="danger" onclick="skipHole()">Skip</button>
</div>
<div id="hole-info" class="hole-info"></div>
<form id="hole-form" onsubmit="event.preventDefault();">
<input type="hidden" id="hole-score-id" />
<div class="form-grid">
<div>
<label for="strokes">Strokes</label>
<input id="strokes" type="number" value="1" min="1" oninput="saveDraft()" />
</div>
<div>
<label for="unfinished">Unfinished (X)</label>
<input id="unfinished" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="fairway">Fairway</label>
<input id="fairway" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="putts-green">Putts Green</label>
<input id="putts-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="putts-off-green">Putts Off</label>
<input id="putts-off-green" type="number" value="0" min="0" oninput="saveDraft()" />
</div>
<div>
<label for="pen-water">Water</label>
<input id="pen-water" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-ob">OB</label>
<input id="pen-ob" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="pen-lost">Lost</label>
<input id="pen-lost" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-greenside">Greenside</label>
<input id="bunker-greenside" type="checkbox" oninput="saveDraft()" />
</div>
<div>
<label for="bunker-fairway">Fairway Bunker</label>
<input id="bunker-fairway" type="checkbox" oninput="saveDraft()" />
</div>
</div>
</form>
<div class="gir" style="margin-top:.75rem;">GIR: <strong id="gir-display">No</strong></div>
</section>
<section id="stats-section" class="card" style="display:none;">
<h2>Stats</h2>
<div id="stats-display" class="stats"></div>
</section>
<div class="theme-switch">
<a href="index.html">Forest</a>
<a href="theme-dark.html">Dark</a>
<a href="theme-editorial.html">Editorial</a>
</div>
</div>
<script src="app.js"></script>
</body>
</html>