mesh-avatar-studio
일러스트를 코딩 에이전트와 로컬 편집기로 2D 메쉬 애니메이션 아바타로 변환
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Web tool for making printable member and leader cards for a church photo board.
Enter names, upload a photo, frame and colour-correct it, and download a
1800×1200 JPEG ready to print and pin up.
pnpm's version is pinned by the packageManager field, so it self-selects the
right release. The first install asks you to approve native build scripts for
esbuild and workerd; these are pre-approved in pnpm-workspace.yaml.README 전문 보기 ↗Web tool for making printable member and leader cards for a church photo board. Enter names, upload a photo, frame and colour-correct it, and download a 1800×1200 JPEG ready to print and pin up.
Live at https://photoboard.mluther.org
| Concern | Choice |
|---|---|
| Build | Vite 8 + TypeScript |
| UI | React 19 |
| Styling | Tailwind CSS 4, themed to match Bootswatch Lux |
| Photo framing | react-easy-crop |
| HEIC support | heic-to, lazily loaded |
| Icons | lucide-react |
| Fonts | Merriweather + Nunito Sans, self-hosted via Fontsource |
| Hosting | Cloudflare Workers (static assets) |
| Packages | pnpm |
pnpm install
pnpm dev
pnpm's version is pinned by the packageManager field, so it self-selects the
right release. The first install asks you to approve native build scripts for
esbuild and workerd; these are pre-approved in pnpm-workspace.yaml.
Other scripts:
pnpm check # everything CI runs, in one go
pnpm build # typecheck, then build to dist/
pnpm preview # serve the production build locally
pnpm typecheck # types only
pnpm lint # eslint
pnpm lint:fix # eslint, fixing what it can
pnpm format # prettier, writing changes
pnpm format:check # prettier, check only (what CI runs)
.github/workflows/ci.yml runs format check, lint, typecheck and build on
every pull request and on pushes to master. The steps use if: !cancelled()
so one failure doesn't mask the rest — a PR shows every problem in a single
run rather than one per push. pnpm check runs the same set locally.
Dependencies must be at least 24h old. pnpm-workspace.yaml sets
minimumReleaseAge: 1440, so a version published in the last day is rejected —
that window is when a compromised package reaches you before anyone has noticed.
pnpm's resolver honours it, so an ordinary ^ range simply resolves to the
newest release that is old enough; you rarely notice it.
If CI reports ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION but the same install
passes locally, it's because pnpm caches a per-lockfile verdict in
~/Library/Caches/pnpm/lockfile-verified.jsonl (~/.cache/pnpm/ on Linux) and
reuses it — the message says "verified Nm ago". Delete that file to force a real
check. To fix a genuine violation, rm pnpm-lock.yaml && pnpm install and let
it re-resolve onto settled versions.
TypeScript is pinned to 6.0.3 on purpose. typescript-eslint hard-errors on TypeScript 7 (it refuses to load, rather than warning), and 6.0.3 is the newest release it supports. Revisit once typescript-eslint#10940 lands; until then, upgrading TypeScript will break linting.
Linting is type-aware (recommendedTypeChecked), so rules like
no-floating-promises can see across the async upload/render/download paths.
pnpm deploy
That builds and runs wrangler deploy, which uploads dist/ to Cloudflare.
You'll need to pnpm exec wrangler login once first.
wrangler.jsonc claims photoboard.mluther.org as a custom domain. For that
to work, the mluther.org zone must be on the same Cloudflare account — once
it is, Cloudflare creates and manages the DNS record itself, so there is
nothing to add by hand. If the zone lives elsewhere, either move it to
Cloudflare or drop the routes block and use the *.workers.dev URL.
The printed card is drawn directly onto a canvas, not screenshotted from the DOM. Everything flows from one place:
src/lib/cardLayout.ts — the card's geometry and type scale, in a 600×400
design space. The export is the same numbers at 3×, giving exactly 1800×1200.src/lib/renderCard.ts — draws a card at any scale from that spec.The live preview and the downloaded JPEG call the same renderer, so the preview cannot drift from what prints. The only part of the preview that isn't canvas is the photo itself, which is the interactive cropper layered over the photo frame; its crop region feeds straight into the export.
Two things worth knowing if you ever adjust the layout:
CARD.textPadLeft / textPadRight are deliberately
asymmetric (5px / 15px), nudging the text block left of true centre to
correct for drift on the printer these are produced on. Retune that pair,
not the centre point, if you change printers.family-placeholder.webp is
trimmed so its very top row of ink is the crown of the father's head, so
PLACEHOLDER_BOX simply puts that edge on the red guide line and the art
demonstrates the alignment it asks the user for. Width is the frame less
PLACEHOLDER_INSET_X, and the aspect fixes the height — which deliberately
overruns the frame, cropping the family mid-shin, so it must be clipped.
Replacement art has to be trimmed the same way, with the tallest head at the
very top, and PLACEHOLDER_ASPECT updated to match.fillText, the text is silently already wrong.
src/lib/fonts.ts awaits every face the card uses before any render. This is
why the fonts are self-hosted rather than pulled from a CDN.The Lux look was reproduced by measuring the old build's computed styles rather than eyeballing screenshots, so a few values look odd but are deliberate:
51fr 106fr 51fr, not 1fr 2fr 1fr. Bootstrap's col-md-3/6/3
sit on a row that overhangs the container's padding, so the real content
columns are 306/636/306 either side of 24px gutters.font-weight: 400, though Lux declares 200. Lux only ever
loaded Nunito Sans 400 and 600, so its 200 had no face to resolve to and
always rendered at 400.-webkit-font-smoothing: antialiased. Lux doesn't set it, and it renders
noticeably thinner and lighter than the platform default.inline-block +
24px line-height, which an icon-only flex button would otherwise collapse.\n in any single-line field to force a line break.This replaced a Sergey-built static site deployed to surge.sh. Things that changed beyond the framework:
html-to-image is gone. It was the source of the intermittent "try
uploading a smaller image" failures; the canvas renderer is deterministic and
has no image-size ceiling.