fix: visual tuning from live preview — diamond position, borders, corners, header z-index

- Move diamond left to align bottom point with center column divider
- Keep all border widths uniform across tiers (remove T4 bold borders)
- Remove corner accents entirely (T4 differentiated by glow + prismatic)
- Fix T4 header z-index: don't override position on absolutely-positioned
  topright stat elements (stealing, running, bunting, hit & run)
- Add ?tier= query param for dev preview of tier styling on base cards
- Add run-local.sh for local API testing against dev database
- Add .env.local and .run-local.pid to .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-04-04 11:20:05 -05:00
parent 830e703e76
commit d92ab86aa7
5 changed files with 146 additions and 27 deletions

2
.gitignore vendored
View File

@ -59,6 +59,8 @@ pyenv.cfg
pyvenv.cfg
docker-compose.override.yml
docker-compose.*.yml
.run-local.pid
.env.local
*.db
venv
.claude/

View File

@ -737,6 +737,9 @@ async def get_batter_card(
variant: int = 0,
d: str = None,
html: Optional[bool] = False,
tier: Optional[int] = Query(
None, ge=0, le=4, description="Override refractor tier for preview (dev only)"
),
):
try:
this_player = Player.get_by_id(player_id)
@ -800,7 +803,9 @@ async def get_batter_card(
card_data["cardset_name"] = this_player.cardset.name
else:
card_data["cardset_name"] = this_player.description
card_data["refractor_tier"] = resolve_refractor_tier(player_id, variant)
card_data["refractor_tier"] = (
tier if tier is not None else resolve_refractor_tier(player_id, variant)
)
card_data["request"] = request
html_response = templates.TemplateResponse("player_card.html", card_data)
@ -838,7 +843,9 @@ async def get_batter_card(
card_data["cardset_name"] = this_player.cardset.name
else:
card_data["cardset_name"] = this_player.description
card_data["refractor_tier"] = resolve_refractor_tier(player_id, variant)
card_data["refractor_tier"] = (
tier if tier is not None else resolve_refractor_tier(player_id, variant)
)
card_data["request"] = request
html_response = templates.TemplateResponse("player_card.html", card_data)

132
run-local.sh Executable file
View File

@ -0,0 +1,132 @@
#!/usr/bin/env bash
# run-local.sh — Spin up the Paper Dynasty Database API locally for testing.
#
# Connects to the dev PostgreSQL on the homelab (10.10.0.42) so you get real
# card data for rendering. Playwright Chromium must be installed locally
# (it already is on this workstation).
#
# Usage:
# ./run-local.sh # start on default port 8000
# ./run-local.sh 8001 # start on custom port
# ./run-local.sh --stop # kill a running instance
#
# Card rendering test URLs (after startup):
# HTML preview: http://localhost:8000/api/v2/players/{id}/battingcard/{date}/{variant}?html=True
# PNG render: http://localhost:8000/api/v2/players/{id}/battingcard/{date}/{variant}
# API docs: http://localhost:8000/api/docs
set -euo pipefail
cd "$(dirname "$0")"
PORT="${1:-8000}"
PIDFILE=".run-local.pid"
LOGFILE="logs/database/run-local.log"
# ── Stop mode ────────────────────────────────────────────────────────────────
if [[ "${1:-}" == "--stop" ]]; then
if [[ -f "$PIDFILE" ]]; then
pid=$(cat "$PIDFILE")
if kill -0 "$pid" 2>/dev/null; then
kill "$pid"
echo "Stopped local API (PID $pid)"
else
echo "PID $pid not running (stale pidfile)"
fi
rm -f "$PIDFILE"
else
echo "No pidfile found — nothing to stop"
fi
exit 0
fi
# ── Pre-flight checks ───────────────────────────────────────────────────────
if [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo "Already running (PID $(cat "$PIDFILE")). Use './run-local.sh --stop' first."
exit 1
fi
# Check Python deps are importable
python -c "import fastapi, peewee, playwright" 2>/dev/null || {
echo "Missing Python dependencies. Install with: pip install -r requirements.txt"
exit 1
}
# Check Playwright Chromium is available
python -c "
from playwright.sync_api import sync_playwright
p = sync_playwright().start()
b = p.chromium.launch(args=['--no-sandbox'])
b.close()
p.stop()
" 2>/dev/null || {
echo "Playwright Chromium not installed. Run: playwright install chromium"
exit 1
}
# Check dev DB is reachable
python -c "
import socket
s = socket.create_connection(('10.10.0.42', 5432), timeout=3)
s.close()
" 2>/dev/null || {
echo "Cannot reach dev PostgreSQL at 10.10.0.42:5432 — is the homelab up?"
exit 1
}
# ── Ensure directories exist ────────────────────────────────────────────────
mkdir -p logs/database
mkdir -p storage/cards
# ── Launch ───────────────────────────────────────────────────────────────────
echo "Starting Paper Dynasty Database API on http://localhost:${PORT}"
echo " DB: paperdynasty_dev @ 10.10.0.42"
echo " Logs: ${LOGFILE}"
echo ""
# Load .env, then .env.local overrides (for passwords not in version control)
set -a
# shellcheck source=/dev/null
[[ -f .env ]] && source .env
[[ -f .env.local ]] && source .env.local
set +a
# Override DB host to point at the dev server's IP (not Docker network name)
export DATABASE_TYPE=postgresql
export POSTGRES_HOST="${POSTGRES_HOST_LOCAL:-10.10.0.42}"
export POSTGRES_PORT="${POSTGRES_PORT:-5432}"
export POSTGRES_DB="${POSTGRES_DB:-paperdynasty_dev}"
export POSTGRES_USER="${POSTGRES_USER:-sba_admin}"
export LOG_LEVEL=INFO
export TESTING=True
if [[ -z "${POSTGRES_PASSWORD:-}" || "$POSTGRES_PASSWORD" == "your_production_password" ]]; then
echo "ERROR: POSTGRES_PASSWORD not set or is the placeholder value."
echo "Create .env.local with: POSTGRES_PASSWORD=<actual password>"
exit 1
fi
uvicorn app.main:app \
--host 0.0.0.0 \
--port "$PORT" \
--reload \
--reload-dir app \
--reload-dir storage/templates \
2>&1 | tee "$LOGFILE" &
echo $! >"$PIDFILE"
sleep 2
if kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
echo ""
echo "API running (PID $(cat "$PIDFILE"))."
echo ""
echo "Quick test URLs:"
echo " API docs: http://localhost:${PORT}/api/docs"
echo " Health: curl -s http://localhost:${PORT}/api/v2/players/1/battingcard?html=True"
echo ""
echo "Stop with: ./run-local.sh --stop"
else
echo "Failed to start — check ${LOGFILE}"
rm -f "$PIDFILE"
exit 1
fi

View File

@ -21,12 +21,6 @@
<div class="diamond-quad{% if refractor_tier >= 3 %} filled{% endif %}" {% if refractor_tier >= 3 %}style="background: {{ filled_bg }};"{% endif %}></div>
<div class="diamond-quad{% if refractor_tier >= 4 %} filled{% endif %}" {% if refractor_tier >= 4 %}style="background: {{ filled_bg }};"{% endif %}></div>
</div>
{% if refractor_tier == 4 %}
<div class="corner-accent tl" style="width: 35px; height: 35px; border-top: 3px solid #c9a94e; border-left: 3px solid #c9a94e;"></div>
<div class="corner-accent tr" style="width: 35px; height: 35px; border-top: 3px solid #c9a94e; border-right: 3px solid #c9a94e;"></div>
<div class="corner-accent bl" style="width: 35px; height: 35px; border-bottom: 3px solid #c9a94e; border-left: 3px solid #c9a94e;"></div>
<div class="corner-accent br" style="width: 35px; height: 35px; border-bottom: 3px solid #c9a94e; border-right: 3px solid #c9a94e;"></div>
{% endif %}
{% endif %}
<div id="header" class="row-wrapper header-text border-bot" style="height: 65px">
<!-- <div id="headerLeft" style="flex-grow: 3; height: auto">-->

View File

@ -8,7 +8,7 @@
.tier-diamond {
position: absolute;
left: 600px;
left: 597px;
top: 78.5px;
transform: translate(-50%, -50%) rotate(45deg);
display: grid;
@ -33,17 +33,6 @@
inset 1px 0 2px rgba(255,255,255,0.15);
}
.corner-accent {
position: absolute;
z-index: 6;
pointer-events: none;
border-style: solid;
border-color: transparent;
}
.corner-accent.tl { top: 0; left: 0; }
.corner-accent.tr { top: 0; right: 0; }
.corner-accent.bl { bottom: 0; left: 0; }
.corner-accent.br { bottom: 0; right: 0; }
{% if refractor_tier == 1 %}
/* T1 — Base Chrome */
@ -169,18 +158,13 @@
}
.border-bot {
border-bottom-color: #c9a94e;
border-bottom-width: 6px;
}
#resultHeader.border-bot {
border-bottom-width: 5px;
border-bottom-width: 4px;
}
.border-right-thick {
border-right-color: #c9a94e;
border-right-width: 7px;
}
.border-right-thin {
border-right-color: #c9a94e;
border-right-width: 4px;
}
.vline {
border-left-color: #c9a94e;
@ -215,7 +199,7 @@
animation: t4-prismatic-sweep 6s linear infinite;
animation-play-state: paused;
}
#header > * { position: relative; z-index: 2; }
#header > * { z-index: 2; }
/* T4 diamond glow pulse — paused for static PNG */
@keyframes diamond-glow-pulse {