Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
알프스·보주산 고개와 고도 프로필·코스·자전거 마을을 한 지도에, 반월별 주행 가능·날씨·기후와 함께
Where to go with the road bike, and when? Passes in the Alps and the Vosges, ascents with elevation profiles, loops and cycling towns on one map – with rideability per half-month, weather and climate.
라이프스타일TypeScript · CSSGitHub @mdugue접속 불안정운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Alpenpässe – where to ride, and when A map for planning road-cycling holidays in the Alps and the Vosges. It answers destination questions, not routing questions: • Which regions are good in early October if we want to ride a few great passes? • Where should we look for a hotel so that several passes and a loop are within reach? • Which destinations should we keep an eye on for single-day and multi-day tours?README 전문 보기 ↗
A map for planning road-cycling holidays in the Alps and the Vosges. It answers destination questions, not routing questions:
Data today: 279 roads with 427 ascents and their elevation profiles, 19 loop
tours, 71 cycling towns and 38 destinations, each with a rideability estimate
for a freely chosen half-month, a 7-day forecast and a 2015–2024 climate
series at the summit, in 2D and 3D. The UI is in German, and in English under
/en.
What it is not. A route planner or a navigation tool. Komoot, Strava and similar services do turn-by-turn planning far better, and the app links out to them. Precision beyond "is this doable that week" is out of scope, and so are GPX export, custom tour building and live navigation.
bun install
bun run data:build # once: precompute routes, elevation profiles, climate
bun dev
Without data:build the app still starts; only the drawn roads, the profiles
and the climate series are missing.
| Command | Purpose |
|---|---|
bun dev | Development server |
bun run build / bun start | Production build and server |
bun run typecheck | next typegen && tsc --noEmit – the route types first, then the compiler |
bun run lint / bun run lint:fix | oxlint + oxfmt via ultracite (React Compiler and type-aware rules incl.) |
bun run seams / bun run palette | The two repo checks inside lint: the adapters, and the sRGB mirror |
bun run data:build | Fetch routes, elevation profiles, climate → data/generated/ (resumable) |
bun run data:build --status | Show what is still missing and what it costs in Open-Meteo calls |
bun run data:check | Validate references and completeness of the data |
bun run data:locate [slug…] | Where a pass point belongs: DEM, road distance, OSM candidates (network) |
bun run data:photos | Wikimedia Commons photos per entity → data/generated/photos.json |
bun run data:schema | Re-emit data/schema/*.schema.json from lib/schema.ts |
bun run data:backfill | Drain the whole precomputation backlog in hourly batches |
bun run test | Unit tests next to the code |
bun run e2e | Build and drive the app in a headless Chrome |
bun run docs:diagrams | Render the docs' Mermaid blocks to docs/diagrams/*.svg (commit them) |
bun run map:glyphs | Rasterise Inter into MapLibre glyph atlases (committed, rarely needed) |
bun run ui:init / bun run ui:add | (Re)install the shadcn "mira" preset and components |
All content data lives as JSON in the repo (data/), is imported at build
time and read synchronously in lib/data.ts inside the one "use cache" on
app/[lang]/(explorer)/layout.tsx – the start page and every entity route
under it are therefore fully prerendered, once per language. Two kinds of data never become
React props: the route geometry, written as content-hashed GeoJSON into
public/map for MapLibre to fetch and tile in its worker, and what only one
entity's panel reads (its elevation profiles and photo metadata), written as
one content-hashed JSON per entity into public/detail; every later change of
period, filter or selection reaches the lines as feature state rather than as
new data. The
only dynamic source is the weather forecast; it is streamed into the pass
route with its own cache lifetime (lib/weather.ts) so Open-Meteo is
queried once per pass and hour instead of once per visitor. The selection is
the path and everything else is the hash, so every view is shareable and the
back button closes the panel:
https://alpen.manuel.fyi/pass/col-du-galibier#t=10&z=9&c=45.06,6.41
└── selection ──┘ └ period ┘ └── camera ──┘
Every pass, tour, town and destination is a prerendered route with its own
title, description and share image (app/[lang]/(explorer)/[kind]/[slug]); the
layout around it – map, lists, season bar – stays mounted while the path
changes (lib/hash-adapter.ts turns the path into the reducer's select and
back, and the state into router.push):
sequenceDiagram
participant U as Visitor
participant L as (explorer)/layout<br/>map + sidebar, stays mounted
participant R as Next router
participant P as [kind]/[slug]/page<br/>prerendered
U->>L: taps a row or a marker → reduce(select)
L->>R: router.push("/pass/x" + location.hash)
R->>P: fetches the route's payload
P-->>L: the detail slot – the weather streams into its Suspense hole
U->>R: browser back
R-->>L: pathname "/" → reduce(back); the map stays where it is
flowchart LR
J["routes.json"] --> B["build-map-assets.ts<br/>simplify 5 m, content hash"]
B --> G["public/map/routes.a1b2c3d4.geojson<br/>tours.e5f6a7b8.geojson<br/>immutable, cached for a year"]
G -- "fetched once by MapLibre,<br/>tiled in its worker" --> M["sources with promoteId"]
P["page props<br/>passes, tours, towns, climate, asset URLs"] --> X["Explorer"]
X -- "period, filter or<br/>selection change" --> F["setFilter + setFeatureState<br/>status, selected"]
F --> M
M --> L["layers read<br/>feature-state in paint"]
X -- "selection" --> D["public/detail/<entity>.<hash>.json<br/>profiles + photos, ~2 KB"]
Where the data in those files comes from – which host answers which question,
what each command writes and the states a route passes through – is
docs/data-pipeline.md.
Two languages, one tree (plan 08): every route lives under app/[lang],
German stays prefix-free and canonical, English lives under /en, and
next.config.ts rewrites the prefix-free paths onto /de (and redirects a
typed /de/… to the prefix-free path), both prerendered. The one request
that is negotiated is the bare root: proxy.ts sends a browser that asks for
English to /en, unless the language menu's cookie says otherwise:
| Request | Rewrite | Route file | Language |
|---|---|---|---|
/ | → /de | app/[lang]/(explorer)/page.tsx | de |
/pass/col-du-galibier | → /de/pass/… | app/[lang]/(explorer)/[kind]/[slug]/page.tsx | de |
/en | (none) | app/[lang]/(explorer)/page.tsx | en |
/en/pass/col-du-galibier | (none) | app/[lang]/(explorer)/[kind]/[slug]/page.tsx | en |
/impressum, /en/impressum | → /de/…, (none) | app/[lang]/impressum/page.tsx | de (both) |
The words are lib/i18n/messages.de.ts (the source, typed) and
messages.en.ts (held to its shape, so a missing key is a type error), and
each page ships only its own; the curated prose is data/i18n/en/*.json,
merged over the German records in lib/data.ts with a German fallback that
bun run data:check counts. The language is the last group of the map's
"…" menu: a plain link to the same view under the other prefix, hash and
all.
app/ [lang]/: layout, the explorer layout with the start page and
the entity routes, Impressum, Datenschutz, the share images;
at the root the metadata routes (icons, manifest, robots, sitemap)
components/ explorer (state) · map (MapLibre) · sidebar (lists, filters) · panel (detail) · ui (shadcn)
data/ passes.json, tours.json, towns.json, destinations.json ← source data, hand-maintained
data/i18n/en/ the curated prose in English, keyed by slug, hand-checked
data/generated/ summits, routes, routes-meta, rejected, profiles, climate, photos
← from data:build and data:photos, committed
data/schema/ JSON Schema for the editor, from data:schema
lib/ types, data access, status heuristic, state hooks, brand constants, i18n/ (the words)
scripts/ build-data.ts (precomputation), check-data.ts (validation),
locate-pass.ts (where a pass point belongs), build-photos.ts,
build-map-assets.ts / build-detail-assets.ts (→ public/, git-ignored)
test/, e2e/ unit tests and the headless-browser suite
docs/ pipeline, data model, scales, conventions, roadmap, plans/,
guide/ (German, for users), diagrams/ ← published under /wissen
.agents/skills/ project skills for agents: implement-plan, curate-data, preview-app
AGENTS.md is the index for everything below it: the product
goal, the principles, a map of the code and the one-line version of every
convention, each linking to the document that explains it. CLAUDE.md is a
symlink to it, so there is only ever one copy. The site publishes all of
docs/ under /wissen: a German guide for the people who use the map
(docs/guide/) and the developer documents below (see
"The docs are the site").
| Document | What it answers |
|---|---|
docs/data-pipeline.md | where every number comes from: sources, commands, states, what it costs |
docs/data-model.md | what a field means, the schemas, the route quality gate |
docs/scales.md | the 1–5 scales, the status ladder, the reach bands |
docs/ui-conventions.md | how the interface is built and why |
docs/map-rendering.md | camera, layers, hit testing, basemap |
docs/architecture.md | what the page ships, caching, the one dynamic route, the toolchain |
docs/plans/README.md | what is being built next |
docs/roadmap.md | what lies beyond the plans |
docs/plans/ holds one implementation plan per topic, ordered by how much
each serves the destination goal: data quality gate, payload, period as the
hero, climate-aware status, destinations, filters and search, real routes,
basemap, schema validation, tests, smaller items, English toggle, profile
interactivity. Start with docs/plans/README.md.
.agents/skills/ holds project skills that agents (and people) follow:
implement-plan executes a plan end to end, curate-data adds or edits
passes, tours and towns, preview-app builds and screenshots the app headless
to verify a UI change. Vendored general-purpose skills (shadcn, React,
composition patterns, code review) sit next to them.
See .env.example. None of them is required to start the app.
ORS_KEY – only for data:build. Without the key the script routes via the
public OSRM demo server using the car profile; with the key it uses the
OpenRouteService road-cycling profile, which correctly handles the
Tremola cobbles, the gravel on the Finestre and car-free roads. Free,
2,000 routes/day: https://openrouteservice.org/dev. The script asks
api.heigit.org/openrouteservice; the old host api.openrouteservice.org
is capped at 200 a day and shuts down in November 2026.OPEN_METEO_BUDGET – only for data:build. Open-Meteo bills weighted
"calls" (an elevation profile ≈ 100, a climate series ≈ 261) with a free
tier of 5,000/hour and 10,000/day. The script stops itself at this budget
(default 4,500) and picks up the rest on the next run; the GitHub Action
refresh-data.yml runs on a push to data/*.json, and
bun run data:backfill drains a larger backlog locally in hourly batches.NEXT_PUBLIC_SITE_URL – absolute base URL for canonical links, the share
images, robots.txt and sitemap.xml. On Vercel it is derived from
VERCEL_PROJECT_PRODUCTION_URL (or VERCEL_URL on a preview), so it is only
needed for a custom domain or a different host.NEXT_PUBLIC_THUNDERFOREST_KEY, NEXT_PUBLIC_MAPTILER_KEY – optional
outdoor base maps. The default is the app's own vector style on OpenFreeMap
tiles (light or dark with the OS, no key); OSM, OpenTopoMap, CyclOSM, Esri
Topo and satellite imagery are available as alternatives.Push the repo to GitHub, import it in Vercel, done – no vercel.json needed.
Every page is prerendered at build time and served from the CDN edge; only
the forecast streamed into a pass's page runs as a function. Production: https://alpen.manuel.fyi.
Grew out of a single HTML prototype; the data and the status heuristic were
carried over from it. The prototype is kept for reference at
docs/prototype.html.
The code and the curated content (data/*.json, data/i18n/) are licensed
under the Apache License 2.0. What the app takes from elsewhere
keeps its own licence: the routed geometry is derived from OpenStreetMap
(ODbL), elevations and climate come from Open-Meteo (CC BY 4.0), the photos
belong to their authors on Wikimedia Commons, and the fonts are under the SIL
Open Font License. NOTICE has the details and the credits.