Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Centralized, searchable schedules for San Francisco public swimming pools. This app scrapes official pool schedule PDFs from SF Rec & Park, uses an LLM to extract structured schedules, and provides a clean UI to browse by program, day, time, and pool.
• Next.js (App Router), React 19, TypeScript
• Tailwind CSS v4 (via @tailwindcss/postcss and @import "tailwindcss")
• Vercel AI SDK (ai) with Google Generative AI provider (@ai-sdk/google)
• Zod for strict schema validation
• GitHub Actions for automated weekly schedule updates
• Node.js 22+
• npm
• A Google Generative AI API keyREADME 전문 보기 ↗Centralized, searchable schedules for San Francisco public swimming pools. This app scrapes official pool schedule PDFs from SF Rec & Park, uses an LLM to extract structured schedules, and provides a clean UI to browse by program, day, time, and pool.
@tailwindcss/postcss and @import "tailwindcss")ai) with Google Generative AI provider (@ai-sdk/google)Install dependencies:
npm install
Create .env.local in the project root and add:
GOOGLE_GENERATIVE_AI_API_KEY=your_key_here
Run the dev server:
npm run dev
Generate schedules:
npm run build-schedules
This will scrape PDF URLs, download PDFs, extract schedules, and write public/data/all_schedules.json. View at /schedules.
data/pools.json — Source of truth for static pool metadata (id, name, shortName, address, pageUrl). Does not change frequently.public/data/discovered_pool_schedules.json — Scraped PDF URLs (poolId → pdfUrl mapping). Regenerated on each scrape.data/pdf-manifest.json — Tracks downloaded PDFs by hash to detect changes.data/extracted/<poolId>.json — Cached LLM extractions per PDF, keyed by PDF hash in data/extracted/_manifest.json. Committed, so the weekly run only sends a PDF to the model when it has actually changed.public/data/all_schedules.json — Aggregated schedule data for the UI.data/changelog/ — Change history between schedule updates.scrape → download-pdfs → process-all-pdfs
↓ ↓ ↓
validates checks hash preserves data
pool count downloads if fails on large
& URLs content changed changes
pools.json — fails if pool count or page URLs change unexpectedly.The pipeline computes a changelog comparing old vs new schedules:
Set FAIL_ON_LARGE_CHANGES=false locally to bypass this check during development.
npm run dev — start Next.js dev servernpm run build — build for productionnpm run start — start production buildnpm run lint — run ESLintnpm run test — run testsnpm run scrape — scrape pool pages, validate against pools.json, discover PDF URLsnpm run download-pdfs — download changed PDFs into data/pdfs/npm run process-all-pdfs — extract schedules from PDFs, preserve unchanged, write changelognpm run build-schedules — full pipeline: scrape → download → processnpm run scrape-alerts — scrape pool alerts from SF Rec & Parknpm run scrape-holidays — fetch the city holiday list from sf.gov into public/data/city_holidays.json (-- --force to fetch when the year is already saved); the "Update City Holidays" workflow runs it daily from December through Februarynpm run analyze-programs — analyze raw vs canonical program namespools.json (address, URLs)GitHub Actions runs weekly to:
public/data/ and data/changelog/If large changes are detected, the build fails and a notification is sent for manual review.
# Required: Google AI API key for schedule extraction
GOOGLE_GENERATIVE_AI_API_KEY=your_key_here
# Optional: Disable build failure on large changes (for local dev)
FAIL_ON_LARGE_CHANGES=false
# Optional: Force re-extraction even if cache exists
REFRESH_EXTRACT=1
# For CI notifications (GitHub Actions secrets)
PUSHOVER_USER_KEY=...
PUSHOVER_API_TOKEN=...
The animated pool header is a two-pass WebGL pipeline run by p5.js in src/components/HeaderAnimation.tsx.
Pass 1 — simulation (src/components/water-sim.frag). A real water heightfield is simulated with the discrete 2D wave equation over a small float texture:
next = (2·h − hPrev + WAVE_SPEED · laplacian(h)) · DAMPING
Each texel stores the current height in the red channel and the previous frame's height in green; two framebuffers ping-pong so one frame's output is the next frame's input. Everything watery — rings expanding, waves interfering, reflections off the pool edges (clamped texture sampling acts as walls) — emerges from this one equation. Nothing else animates the ripples.
Disturbances are injected into the field each frame:
mix) rather than adding, so holding the pointer in place can't stack the dent infinitely deep.SCROLL_COOLDOWN_MS) so scrubbing the scrollbar can't pump the pool into chaos. Per-column hash jitter roughens the line so it doesn't read as a ruler-straight artifact.The texture is sized in CSS pixels (SIM_TEXEL_CSS_PX, ~3px per texel, square on screen) so ripples have the same on-screen wavelength and speed on every device, from phone to ultrawide.
Pass 2 — display (src/components/header-shader.frag). Renders the tiles, "SF POOLS" text, and lighting. It samples the heightfield (height + gradient via central differences) and combines it with a cheap analytic ambient swell (four drifting sines). The combined wave gradient drives everything, which is what makes the effects feel physically coherent:
x/(1+x) curves so extreme waves can't blow out to white or blackpow(dot(N, L), 64) sparkle on slopes tilted toward the light, scaled by local ripple energy so glints fade with the wave rather than snapping off| Knob | Where | Effect |
|---|---|---|
WAVE_SPEED | water-sim.frag | Ring travel speed. Must stay < 0.5 (CFL stability limit) |
DAMPING | water-sim.frag | How long waves live; toward 1.0 = longer sloshing |
SIM_SUBSTEPS | HeaderAnimation.tsx | Sim steps per frame; overall simulation speed |
SIM_TEXEL_CSS_PX | HeaderAnimation.tsx | Master scale: bigger = chunkier, softer water everywhere |
SIM_IMPULSE_RADIUS | HeaderAnimation.tsx | Size of the pointer's dent, in sim texels |
impulseAmp formula | HeaderAnimation.tsx | How hard moves/clicks press into the water |
SCROLL_AMP_PER_PX, SCROLL_AMP_MAX | HeaderAnimation.tsx | Scroll slosh strength per unit of scroll acceleration, and its cap |
SCROLL_COOLDOWN_MS | HeaderAnimation.tsx | Min gap between scroll sloshes; higher = calmer under scrubbing |
SIM_GRAD_SCALE | header-shader.frag | How strongly ripples refract tiles / bend caustics / glint |
SIM_HEIGHT_SCALE | header-shader.frag | How strongly crests brighten and troughs darken |
refraction 0.035 | header-shader.frag main() | Overall "looking through water" distortion |
caustic coupling 0.6 | header-shader.frag main() | How much waves warp the caustic pattern |
dispersion exponents 1.25 / 0.8 | header-shader.frag main() | Chromatic fringing around caustic highlights |
| glint constants | header-shader.frag main() | Sparkle tightness (64), threshold (0.05), brightness (0.4) |
Two structural constraints worth knowing before tweaking: sim texels must stay square on screen (the JS derives texture height from the clamped width — don't size the two axes independently) or waves propagate anisotropically; and windowResized deliberately ignores height-only resizes because iOS fires them as browser chrome collapses during scrolling — recreating the framebuffers there would blank the water mid-slosh.
MIT