Created
July 5, 2026 14:21
-
-
Save sparr/105a45689768f103decc1459156a12f6 to your computer and use it in GitHub Desktop.
Philadelphia Police Pools Budget Balancer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Philadelphia Police vs Pools Budget Balancer</title> | |
| <style> | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; | |
| background: #eee; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 100vh; | |
| margin: 0; | |
| } | |
| h1 { | |
| font-size: 24px; | |
| color: #222; | |
| margin: 0 0 0 0; | |
| text-align: center; | |
| } | |
| .card { | |
| background: #ffffff; | |
| border-radius: 16px; | |
| box-shadow: 0 8px 24px rgba(0,0,0,0.08); | |
| width: 500px; | |
| margin-top: 6px; | |
| margin-bottom: 6px; | |
| padding: 12px 40px; | |
| } | |
| .interface { | |
| padding: 40px 40px; | |
| } | |
| .slider-labels { | |
| display: flex; | |
| justify-content: space-between; | |
| font-weight: 600; | |
| color: #333; | |
| margin-bottom: 8px; | |
| font-size: 14px; | |
| } | |
| input[type="range"] { | |
| width: 100%; | |
| height: 8px; | |
| border-radius: 4px; | |
| background: linear-gradient(to right, #00f, #f00); | |
| outline: none; | |
| -webkit-appearance: none; | |
| margin-bottom: 32px; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| background: #fff; | |
| border: 3px solid; | |
| cursor: pointer; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.2); | |
| } | |
| input[type="range"]::-moz-range-thumb { | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| background: #fff; | |
| border: 3px solid; | |
| cursor: pointer; | |
| box-shadow: 0 2px 4px 4rgba(0,0,0,0.2); | |
| } | |
| .stats { | |
| display: flex; | |
| justify-content: space-between; | |
| gap: 20px; | |
| } | |
| .stat { | |
| flex: 1; | |
| text-align: center; | |
| background: #eee; | |
| border-radius: 12px; | |
| padding: 20px 10px; | |
| } | |
| .stat .label { | |
| font-size: 16px; | |
| color: #444; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| margin-bottom: 6px; | |
| } | |
| .stat .value { | |
| font-size: 28px; | |
| font-weight: 700; | |
| color: #222; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Philadelphia Budget Balancer</h1> | |
| <p>by <a href="https://github.com/sparr">sparr</a></p> | |
| <div class="card interface"> | |
| <div class="slider-labels"> | |
| <span>More Pools</span> | |
| <span>More Police</span> | |
| </div> | |
| <input type="range" id="slider" min="0" max="63" value="63"> | |
| <div class="stats"> | |
| <div class="stat"> | |
| <div class="label">Pools</div> | |
| <div class="value" id="poolsValue">All 63 Closed</div> | |
| </div> | |
| <div class="stat"> | |
| <div class="label">Police Officers</div> | |
| <div class="value" id="policeValue">6500</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <p>City budget documents and controller reports establish a total average cost of ~$135k/yr/officer, including base salary, overtime, and benefits.</p> | |
| <p>Approximately 25% of the $84M Parks & Recreation Department budget is spent on pools (maintenance, lifeguards, supplies, other staff), or ~$333k/yr/pool.</p> | |
| </div> | |
| <script> | |
| const slider = document.getElementById('slider'); | |
| const policeValue = document.getElementById('policeValue'); | |
| const poolsValue = document.getElementById('poolsValue'); | |
| const POLICE_MAX = 6500; // at "More Police" end | |
| const POLICE_COST = 135000 | |
| const POOLS_MAX = 63; // at "More Pools" end | |
| const POOLS_MIN = 0; // at "More Police" end | |
| const POOLS_COST = 333333 | |
| function update() { | |
| // t = 0 -> full "More Pools", t = 1 -> full "More Police" | |
| const t = slider.value / slider.max; | |
| const pools = Math.round(POOLS_MAX - t * (POOLS_MAX - POOLS_MIN)); | |
| const police = POLICE_MAX - Math.ceil(pools * POOLS_COST / POLICE_COST); | |
| policeValue.textContent = police.toLocaleString(); | |
| if (pools === POOLS_MIN) { | |
| poolsValue.textContent = "All " + POOLS_MAX + " Closed"; | |
| } else if (pools === POOLS_MAX) { | |
| poolsValue.textContent = "All " + POOLS_MAX + " Open"; | |
| } else { | |
| poolsValue.textContent = pools; | |
| } | |
| } | |
| slider.addEventListener('input', update); | |
| update(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment