WallpaperMachine
당신이 사랑하는 배경화면, 맥에서 새롭게 만나보세요
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기SF street-parking rules on a map — street sweeping, meters & permit zones, with calendar reminders + Web Push move-your-car alerts. Vanilla JS PWA.
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Know where to park in SF. Live at curb.guide.
San Francisco posts a 2-hour street-cleaning window. We pulled about 1.35 million real citations (over a million matched to their exact blocks): on the median block, most of the tickets land inside a ~20-minute span. CURB puts that on a map — every curb in SF colored by its next sweep, with the posted schedule AND the times tickets are actually written there, plus permit (RPP) areas, meters, loading zones (including the unmetered white school zones the city doesn't publish on DataSF), and one-tap Web Push move-your-car alerts.
The whole app is one static index.html — vanilla JS + Leaflet, no framework, no
build step — plus a few Vercel serverless functions for push and share pages, and
precomputed JSON data assets rebuilt by scripts/build-*.mjs. Everything runs on
free tiers. Free, no accounts, no ads, no cookies.
Read the data story: curb.guide/tickets · How it works: curb.guide/about · Contributions welcome — see CONTRIBUTING.md · Security: SECURITY.md
npm install # for the web-push dep used by the API
npm run dev # = npx serve . -l 3077 (http://localhost:3077)
Open http://localhost:3077. A localhost origin is needed for geolocation + service worker.
npm run deploy # = vercel
The "🔔 Sweep alerts" button in the detail sheet subscribes the device to Web Push and saves its spot; a scheduled sender (Upstash QStash every 15 min, GitHub Actions as backup) pushes "move your car" before the next sweep.
Setup (one time):
npx web-push generate-vapid-keys. The public key is embedded in
index.html (VAPID_PUBLIC_KEY); both keys go in env (see .env.example). If you
rotate keys, update the constant in index.html too.KV_REST_API_URL / KV_REST_API_TOKEN automatically. api/_store.js
also accepts UPSTASH_REDIS_REST_URL / _TOKEN for a standalone Upstash DB.VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT,
CRON_SECRET, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY, optional HC_PING_URL,
plus the KV vars from step 2. The sender refuses to run unless a QStash signature or the
CRON_SECRET bearer checks out.vercel.json runs at :07/:22/:37/:52 (needs Vercel Pro;
remove the crons block before any move to Hobby, which rejects sub-daily crons).
https://curb.guide/api/send-notifications (exactly — no query string), cron */15 * * * *,
method POST, empty body, retries 0-1. Copy the QStash signing keys into the two env vars above;
the endpoint verifies every call's signature, so QStash never needs CRON_SECRET..github/workflows/sweep-alerts-cron.yml curls the endpoint with
Authorization: Bearer <CRON_SECRET> (repo secret). GitHub schedules are best-effort (they ran
~7x/day in Sep 2026), so it only fills gaps. Overlapping runs never double-notify.HC_PING_URL = a healthchecks.io check (period 15 min, grace ~30 min); the
endpoint pings it after every successful QStash run and /fail on errors.
GET /api/send-notifications?status=1 with the CRON_SECRET bearer shows the last run, and the
monitor workflow alerts when the latest run failed or, once QStash has run, when no QStash run
succeeded for 40 min. Until QStash first runs, only the GitHub backup sends, hours apart, so the
monitor then alerts only after 8 h without a successful run.iOS — two paths:
APNS_KEY_P8_B64 (or APNS_KEY_P8), APNS_KEY_ID, APNS_TEAM_ID, APNS_BUNDLE_ID on Vercel
(see .env.example / docs/native-push-plan.md).With a Google Map Tiles API key the basemap uses official Google tiles; without one it falls back to keyless CARTO Voyager. The key is a client key — **restrict it by HTTP referrer
http://localhost:3077/*, https://*.vercel.app/*, and
your domain).The key is kept out of this public repo:
config.example.js → config.js (gitignored) and paste your key.GMAPS_KEY in your Vercel env. api/config.js serves it to the client and
vercel.json rewrites /config.js → /api/config, so nothing changes in index.html.yhqp-riqs8vzz-qzz9hi6h-neyh (2017 set; may be incomplete)3mea-di5p (Enterprise Addressing System, nightly)6cqg-dxku (Meter Operating Schedules) ⋈ metersnpm run build:whitezones (data DataSF excludes)ab4h-6ztd) — precomputed into
data/enforcement.json by npm run build:enforcement (see scripts/build-enforcement-records.py)npm run build:stats → data/stats.json (yearly fines,
violations, hour histograms, neighborhood totals + five-year surge)j2bu-swwd (Analysis Neighborhoods) — /tickets + the per-hood pagesAll DataSF datasets are published under the Open Data Commons PDDL; the code here is MIT (see LICENSE).
The posted street sign is always the source of truth. "Ticketed ~" times are historical guidance from past citations, not a guarantee. There is no live space-availability data for SF (SFpark sensors retired in 2014).
See CLAUDE.md for architecture and data schemas, docs/ for the sweeper-data research
and ready-to-send public-records requests.
The headline finding — "the median block is ticketed within ~20 minutes" — is built offline by
scripts/build-enforcement-records.py + scripts/build-stats.mjs:
ab4h-6ztd places each ticket only by its typed address). The older address-only path — normalize to
stripZeros(number)|UPPERCASE(street) and join to a block (CNN) via the Enterprise Addressing System
3mea-di5p, in scripts/build-enforcement.mjs — survives only as a pre-2024 fallback.yhqp-riqs geometry), recovering ~1,050,000 of ~1,350,000 tickets — far
more, and more accurately, than the lossy address join.data/enforcement.json stores, per block side,
[count, mean-minute-of-day, earliest, latest]. The window is really ~20 minutes — the median
ticket lands ~25 min in, and ~77% of tickets fall within the first 45 minutes (~90% within the first
hour). A companion dataset — the city's actual sweeper GPS (request #26-5451 → data/sweeps.json
via scripts/build-sweeps.py) — shows tickets land a median of ~19 minutes after the sweeper passes.Known limits (also stated on the About page): it's a GPS point snapped to the nearest street segment (within 40 m), so any single block can be off; the distribution is conditional on enforcement happening — it shows when tickets land, not whether a block is swept (survivorship); the ticket times are a June 2026 snapshot rebuilt by hand, not by the monthly refresh; and it's predictive from history, never live (SF publishes no real-time sweeper GPS). The posted sign is always the source of truth.
The inferred truck-route layer is a Beta estimate: blocks sharing a corridor + sweep window are
ordered by their mean ticket time; runs whose time-vs-position correlation is weak (|r| < 0.35) are
not drawn.
npm run og # rebuild og.png (1200x630 social card) from og/template.html
npm run build:enforcement # rebuild data/enforcement.json from ~2yr of citations (~2 min)
.github/workflows/monitor.yml