VeloAva
Strava 라이드 데이터를 분석해 사이클리스트 아바타를 진단합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Distance–Time–Speed calculator for runners. Features two distinct modes: a Single pace calculator (enter any two of time, distance, or speed/pace to compute the third) and a Multi pace workout planner (upload a GPX route, divide it into segments, and plan varying paces across elevation profiles). Metric and imperial units. Static HTML with modular ES modules. Mobile-first layout with responsive design.
.gpx routes (5km to 45km) into 100m pace blocks+ Add Zone and Delete actions to manage pace zones123456 → 01:23:45) or pace (e.g. 530 → 05:30)| Layer | Technology |
|---|---|
| UI | Static HTML, vanilla JS ES modules |
| Styling | CSS inlined from css/styles.css, self-hosted JetBrains Mono |
| Persistence | localStorage, cache-first service worker (sw.js) |
| Hosting | Vercel (static) |
flowchart TB
subgraph UI [UI Layer]
Mode[Mode Toggle]
subgraph SingleUI [Single Mode]
Fields[Fields: Time/Dist/Speed/Pace]
Circles[Target Circles]
end
subgraph MultiUI [Multi Mode]
Upload[GPX Upload]
Chart[Elevation Chart]
Zones[Pace Zones]
end
Toggles[Unit Toggle]
Reset[Reset Buttons]
end
subgraph Logic [JS Modules]
Calculator[calculator.js]
Normalize[normalize.js]
Convert[convert.js]
GPX[gpx.js]
Multi[multi-pace.js]
end
subgraph Storage [Local Storage]
LS[(localStorage)]
end
Mode --> Calculator
SingleUI --> Calculator
MultiUI --> Multi
Upload --> GPX
GPX --> Multi
Calculator --> Normalize
Calculator --> Convert
Multi --> Convert
Multi --> Normalize
Calculator --> LS
Multi --> LS
DTS-Calc/
├── index.html # App entrypoint (styles inlined for first paint)
├── sw.js # Cache-first shell, one build per cache
├── css/
│ └── styles.css # Source styles; inlined into index.html
├── js/
│ ├── calculator.js
│ ├── viewport-lock.js # Keeps the fixed app above the keyboard
│ ├── normalize.js
│ ├── convert.js
│ ├── gpx.js
│ └── multi-pace.js
├── assets/
│ ├── icon.svg # 8×8 header glyph (transparent, black pixels)
│ ├── dts-calc-favicon.svg # Browser-tab icon (10×10, white on #C20707)
│ ├── dts-calc-favicon-32.png
│ ├── dts-calc-favicon-16.png
│ ├── home-screen-icon.svg # Home-screen source: 8×8 glyph, 2-cell padding (12×12)
│ ├── apple-touch-icon.png # 180×180 home-screen icon
│ ├── icon-192.png # 192×192 installed-app icon
│ ├── icon-512.png # 512×512 installed-app icon
│ └── icon-maskable-512.png # 512×512 maskable icon (same tile; padding covers the safe zone)
├── assets/fonts/
│ ├── jetbrains-mono-latin.woff2 # Latin variable font, axis 400–800
│ └── OFL.txt
├── manifest.json # App name and home-screen / installed-app icon set
├── vercel.json # Rewrite `/` → index.html; `sw.js` is `Cache-Control: no-cache`
└── README.md
The in-app header mark is the inline 8×8 glyph in index.html (same cells as assets/icon.svg). The browser tab uses assets/dts-calc-favicon.svg (<link rel="icon" type="image/svg+xml">) plus dts-calc-favicon-32.png and dts-calc-favicon-16.png. The manifest does not list the favicon.
Home-screen and installed-app icons are a separate tile: the 8×8 glyph in white #FFFFFF on app red #C20707, with 2 grid squares of padding (12×12) and square corners. assets/home-screen-icon.svg is that source. apple-touch-icon.png, icon-192.png, icon-512.png, and icon-maskable-512.png are nearest-neighbour scales of the 1024×1024 master, opaque RGB with no alpha. The manifest points at those files, not the favicon.
| Route | Purpose |
|---|---|
/ | Main calculator (rewrites to index.html) |
compute() (200ms)compute()lastEdited (PACE or SPEED) decides which drives the othersaveState() / loadState() applied on init, compute, clear, and reset events for both modes separately.iOS 26 draws a frosted scroll-edge over the header of an installed home-screen app when html / body can scroll. That was happening here because .ctds-multi-shell was 100dvh under the header (about 82px past the viewport) and the chart height subtracted a hardcoded --mp-header-h of 48px.
html and body now fill the viewport with overflow: hidden and overscroll-behavior: none (no rubber-band). The header stays a flex row at the top of .ctds-app, padded by the existing safe-area inset. .ctds-scroll is the only scroller (overflow-y: auto, overscroll-behavior: contain). Multi mode fills the space under the header when it fits. On a short or landscape screen the elevation panel keeps a minimum height and .ctds-scroll moves so the totals, chart, and footer stay reachable. viewport-fit=cover lets the existing safe-area padding clear the status bar. theme-color is the solid app red and there is no status-bar meta — neither one is the frost.
When the keyboard opens, viewport-lock.js sizes the app to the visible viewport so inputs and the pace sheet stay reachable. The document does not scroll.
123456) → 01:23:45 on blur530) → 05:30 on blurPrerequisites: None to read the page. The service worker needs an http origin, so use a local server rather than opening the file directly.
For regression tests and manual verification, see CONTRIBUTING.md.
python3 -m http.server 8000
# Visit http://localhost:8000/
After editing css/styles.css or any other shell file, run npm run build. That inlines the stylesheet, stamps the service worker's build hash, and copies the shell into dist/. Running it again on an up-to-date tree does not edit committed files. The build uses only Node's own modules, so it does not need the test dependencies. Vercel runs the same command and publishes dist/ (framework is null, outputDirectory is dist). CI fails if the build would edit a committed file.
The home-screen icon opens start_url /, which is inside manifest scope /. There is no redirect. index.html contains the full stylesheet, so the first paint does not wait on a second CSS request or on fonts.googleapis.com. JetBrains Mono is a self-hosted latin subset with font-display: swap (the face is discovered in that first HTML response, so it is not preloaded). The font file's weight axis is 400–800 and the face exposes that range. Text uses monospace until the file arrives, then the same family and tabular figures as before.
sw.js is registered from the load event. It is cache-first for one whole shell (HTML, JS, font, icons). The build hash in the cache name is the hash of those files, stamped by npm run build, so a deploy installs a new worker that precaches the full list. Cached HTML points its scripts and font at /b/<hash>/.... Icons and the manifest stay on stable URLs such as /assets/apple-touch-icon.png and /manifest.json, so a home-screen fetch that does not go through the worker still finds them. vercel.json rewrites /b/<hash>/... to the same file in dist/. A later script request is served from that build's cache, including Multi opened after the worker process has stopped. If that older cache has been evicted, the request falls through to the current file until the next navigation. A navigation also refetches the whole list with cache: 'no-cache' into a new cache and publishes it only when every file is stored. If the current build cache is missing while its meta entry remains, that launch reads the shell from the network and stores the cache again. The open that discovers the deploy keeps the previous shell, including Multi if it is opened later. The second launch after the deploy runs the new shell. Activate deletes caches from other builds once no open page still needs them; which page needs which build is stored in the cache, not only in the worker process. The worker does not reload open pages. css/styles.css is not requested at runtime, so it is not part of the shell. vercel.json sends Cache-Control: no-cache for /sw.js so the browser can detect a new worker script.
Deploy to Vercel:
vercel
vercel.json rewrites / to /index.html, and /b/<hash>/<path> to /<path>. Those routes apply to the published dist/ files, including /sw.js.