WallpaperMachine
당신이 사랑하는 배경화면, 맥에서 새롭게 만나보세요
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Tell it what you're craving (type it, or tap a pill like Steak or Fried chicken) and get restaurants within 500 m, 5 km or 10 km (500 m is the default): thumbnail, star rating and average price per person. Tap one for details, reviews and TikTok review searches.
Stack: Next.js 16 (App Router, Cache Components) · React 19 · TypeScript · plain CSS (BEM) · Vercel
pnpm install
cp .env.example .env.local # optional, see below
pnpm dev
GOOGLE_MAPS_API_KEY set (Google Cloud → enable Places API (New)), results, photos, prices and reviews are real. The key stays server-side; photos are proxied through /api/photo.Average price comes from Google's priceRange (midpoint); when a place only has a coarse price level it shows $–$$$$ instead.
Google's Places API has no menu data, so lib/menu.ts looks for one on the restaurant's own website (a "Menu" link, a /menu page, or a menu PDF/image). The detail page shows View menu only when one is found, and opens it in a dialog. Sites that forbid framing get an "Open menu" link inside the dialog instead. Coverage depends on the restaurant having a real website: places with no website, or only a Facebook/Instagram page, get no menu button. Fetching is server-side, time-boxed, restricted to public http(s) hosts, and results are cached for a day.
sessionStorage (never in the URL); coordinates are rounded to ~110 m before being sent. If permission is denied, you can search by city/area (Open-Meteo geocoding).src/
app/ routes, route handlers (api/*) and page-level styles
components/
ui/ shared primitives: Thumb, Stars, Status, BackButton
search/ SearchBox (input + food pills)
results/ ResultsView and its parts: toolbar, list, card, pagination, location fallback
restaurant/ detail-page sections: header, info card, reviews, social links
hooks/ client hooks: useCoords (location), useRestaurantSearch (fetch)
lib/
places/ data providers (Google, demo), dedupe, provider switch
*.ts pure helpers: geo, price, sort, paginate, social, foods, types, guard
Each component sits next to its BEM stylesheet, and tests sit next to the code they cover (*.test.ts).
/admin is a small, password-protected dashboard to see Google API usage and rotate the Google Maps key.
| Control | What it does | Needs |
|---|---|---|
| App usage | Daily count of Google calls this app makes (searches, place lookups, photos, errors), last 30 days | Redis (Upstash) |
| Google quota | Request counts by day and response class, plus per-minute rate limits, reported by Google Cloud | A read-only service account |
| Rotate key | Creates a new key (Places API only), tests it, writes it to Vercel and redeploys | Service account with key admin + a Vercel token and deploy hook |
| Revoke previous key | Deletes the old key once the new deployment is live (Google keeps deleted keys 30 days) | Same as rotate |
The dashboard works in tiers: with only ADMIN_PASSWORD + ADMIN_SESSION_SECRET (and Redis) you get sign-in and app usage; each control that still needs setup is shown disabled with the exact env vars it is missing.
ADMIN_PASSWORD and ADMIN_SESSION_SECRET (32+ characters), see .env.example for generators.GOOGLE_SERVICE_ACCOUNT_JSON to the file contents, or its base64 (base64 -i key.json). The project defaults to the one inside the file; override with GCP_PROJECT_ID.main), then set VERCEL_API_TOKEN and VERCEL_DEPLOY_HOOK_URL, plus VERCEL_PROJECT_ID (and VERCEL_TEAM_ID for team projects). Store all of these as sensitive env vars.The repo is public, so all protection comes from secrets in env vars: one password (compared in constant time, 5 attempts per minute per IP), an HMAC-signed HttpOnly, SameSite=Strict session cookie (8 hours), same-origin checks on every request, no caching, and noindex. Every admin action is written to an audit log shown on the dashboard. Google does not expose remaining free credit or spend through any API, so check Billing, Reports in the Cloud console for that.
A scheme of three blues-and-orange hues, with tints and shades of each for the supporting detail. All tokens live in src/app/globals.css, with a warm-dark variant under prefers-color-scheme: dark.
| Share | Hue | Used for |
|---|---|---|
| 60% | Orange #d7733f | header band on every page, warm cream page canvas |
| 30% | Sky blue #72bbe2 | pale-blue cards and panels, selected pills/chips/pages |
| 10% | Blue #2789bd | buttons, links, prices, TikTok tiles, focus |
#2789bd is only 3.9:1, so buttons use the deeper #1f76a5 (5.0:1) and link text #1b6e9b (4.7:1+). The exact #2789bd is kept for focus rings and edges.block__element--modifier). Each component owns one stylesheet named after its block, imported by the component (e.g. search-box.css for SearchBox). Shared tokens, base styles and the page, status and button blocks live in src/app/globals.css.--space-*, --radius-* and --control-height* tokens. Allowed exceptions: 1 px hairline borders and the 4 px focus ring. Type sizes are not on the grid, but line-heights are. pnpm lint:grid enforces this.minmax(0, 1fr), otherwise long names widen the page on phones.pnpm dev · pnpm build · pnpm lint · pnpm lint:grid · pnpm test
MIT © 2026 Ygam Retuta