마음을 한 줄 적으면 명화 세 점이 걸립니다
성공지식백과 유튜브 채널에서 온스페이스(OnSpace AI)로 앱을 하나 만들었습니다. 첫 화면에 오늘 마음을 한 줄 적고 버튼을 누르면 화면이 어두워지고, 스포트라이트 아래로 액자가 내려오면서 진짜 명화가 걸립니다. 옆으로 넘기면 다음 방이 나오고, 방마다 작품 하나와 그 작품이 내 마음과 어떻게 이어지는지 설명하는 해설이 붙습니다. 그림은 뉴욕 메트로폴리탄 미술관이 공개한 실제 소장품이고, 출처를 누르면 미술관 사이트에서 확대해 볼 수 있습니다.
이 앱을 만들면서 코드를 직접 친 부분은 없습니다. 앱 전체가 어떻게 돌아가면 좋겠는지 적은 마스터 프롬프트 한 개를 온스페이스에 넣었고, 미술관 API 연결과 AI 해석, 이메일 인증 로그인, 저장까지 온스페이스가 알아서 붙였습니다. 이 글은 그 영상의 배포 자료입니다. 영상에서 쓴 마스터 프롬프트를 전문 그대로 담았고, 넣는 순서와 막히는 부분, 배포와 결제까지 정리했습니다.
이 가이드의 프롬프트는 온스페이스 새 프로젝트에 그대로 붙여넣는 용도입니다. 아래 링크로 들어가서 계정을 만들고, 새 프로젝트를 여는 화면까지 와 있으면 5분 안에 첫 버전이 나옵니다.
- 마스터 프롬프트 전문영상에서 실제로 넣은 프롬프트를 그대로 복사할 수 있습니다
- 넣는 순서새 프로젝트, Cloud 체크, AI 활성 확인, 첫 테스트까지 단계별로
- 프롬프트가 잠가둔 규칙의 이유왜 라이브러리를 막고, 왜 검색어를 일곱 개로 제한하는지
- 자주 막히는 부분방 2·3이 늦게 나오는 연출, 같은 작품 반복, 벽이 밀리는 현상
- 배포와 결제앱스토어 제출, 안드로이드 경로, RevenueCat 구독 연결 조건
시작 전 준비: 공공 API와 온스페이스
이 앱은 재료 두 가지로 만듭니다. 그림을 가져올 공개 API 하나와, 앱을 만들고 백엔드까지 붙여줄 플랫폼 하나입니다. 둘 다 회원가입 말고는 준비할 게 없습니다.
The Met Collection API
한국에서 화제가 됐던 그늘로 앱은 공공기관이 공개한 데이터를 API로 받아와서 그늘진 길을 알려줍니다. 같은 방식으로 누구나 쓸 수 있는 공개 데이터가 생각보다 많습니다. 이 앱이 쓰는 The Met Collection API는 메트로폴리탄 미술관이 직접 운영하는 공개 엔드포인트입니다. 공공기관은 아니지만 누구에게나 열어둔 API입니다.
2026년 9월 9일 공식 문서 기준으로 확인한 내용입니다.
| 항목 | 내용 |
|---|---|
| 회원가입·API 키 | 필요 없음. 문서에 "register or obtain an API key"가 필요 없다고 명시 |
| 요청 제한 | 초당 80회 이하로 요청 |
| 데이터 라이선스 | 메트가 CC0로 저작권을 포기한 데이터. 허가나 비용 없이 어떤 매체에서든 사용 가능 |
| 검색 | `/search?hasImages=true&q=검색어`로 작품 번호 목록을 받음 |
| 상세 | `/objects/작품번호`로 제목, 작가, 연도, 이미지 주소, 미술관 페이지 주소를 받음 |
| 이미지 조건 | `isPublicDomain`이 참이고 `primaryImageSmall`이 있는 작품만 사용 |
상업용 앱에서도 쓸 수 있다는 점이 중요합니다. 다만 47만 점이 넘는 작품 중 모든 이미지가 공개 도메인은 아니기 때문에, 프롬프트가 공개 도메인 작품만 남기도록 조건을 걸어둡니다.
온스페이스가 맡는 것
온스페이스는 앱을 만드는 데 특화된 플랫폼입니다. 공식 문서 기준으로 React Native와 Expo 위에서 iOS, 안드로이드, 웹 세 곳에서 같은 코드가 돌아가는 앱을 만들어줍니다. 백엔드를 따로 연동할 필요가 없습니다. OnSpace Cloud라는 내장 백엔드가 이메일 인증, 데이터베이스, 파일 저장을 한 프로젝트 안에서 처리합니다.
앱 안에서 AI 기능이 필요할 때도 API 키를 따로 발급받지 않습니다. OnSpace AI는 워크스페이스에 기본으로 켜져 있고, 프롬프트에서 AI가 필요하다고 적으면 온스페이스가 연결합니다. 사용량은 별도 과금이라서 Settings → Account → AI Balance에서 잔액을 볼 수 있습니다.
시작 전 확인
0/4 완료
앱 구조 한눈에 보기
프롬프트를 넣기 전에 결과물이 어떤 화면으로 이루어지는지 알아두면 생성 결과를 판단하기 쉽습니다. 화면은 네 개이고, 그 사이를 흐르는 데이터 경로는 하나입니다.
| 화면 | 하는 일 |
|---|---|
| 질문 화면 | "오늘 마음은 어떤가요?" 아래 입력 카드. 글자 수에 따라 배경색이 천천히 바뀜 |
| 갤러리 | 방 세 개를 옆으로 넘김. 벽, 스포트라이트, 바닥, 액자가 서로 다른 속도로 움직이는 시차 효과. 휴대폰 기울기와 마우스 위치에도 반응 |
| 공개 연출 | 첫 방은 어둡게 시작해서 스포트라이트, 액자 하강, 그림, 캡션 순서로 2.4초 동안 열림. 방 2·3은 처음 보일 때 같은 연출 |
| 작품 상세 | 그림 확대와 켄 번스 효과, 해설 세 줄, 메트 사이트 링크, 저장 버튼 |
| 내 미술관 | 저장한 작품이 벽에 좌우로 번갈아 걸림. 저장은 이메일 인증 로그인 뒤에만 |
데이터는 이렇게 흐릅니다. 사용자가 적은 마음을 온스페이스 AI가 읽고 검색어 하나와 한국어 주제 이름을 고릅니다. 그 검색어로 메트 API를 검색해서 작품 번호를 받고, 상세 정보를 최대 3개씩 동시에 조회하면서 공개 도메인이고 이미지가 있는 작품 3점을 고릅니다. 그림을 미리 내려받는 동안 AI가 두 번째로 호출되어 작품마다 해설 세 줄(마음과의 연결, 볼 만한 요소, 질문 하나)을 씁니다. 영상에서 "이사로 새로운 환경에 와서 너무 신나고 기대돼요"라고 적었을 때 주제는 "설레는 시작"이 됐고, 첫 작품으로 Hope라는 제목의 그림이 걸렸습니다.
마스터 프롬프트 넣는 순서
영상에서는 단계별 프롬프트 팩 대신 전체 단계를 한 번에 요약한 마스터 프롬프트로 만들었습니다. 새 프로젝트에 넣는 순서는 아래와 같습니다.
새 프로젝트에 넣기
프롬프트 전문 복사
아래 코드 블록의 내용을 첫 줄부터 마지막 자기 점검 항목까지 통째로 복사합니다. 일부만 넣으면 뒤쪽 화면이 빠집니다.
온스페이스에서 새 프로젝트 열기
입력창에 프롬프트를 붙여넣습니다.
Cloud 체크
입력창 옆의 Cloud 옵션을 반드시 체크합니다. 이메일 인증과 저장이 이 백엔드 위에서 돌아갑니다. 빼먹으면 로그인과 저장이 만들어지지 않습니다.
Enter, 그리고 AI 활성 확인
생성이 시작되면 AI 기능이 활성화된 상태인지 확인합니다. 사용자의 마음을 AI가 해석해야 하는 앱이라서 꺼져 있으면 검색어를 고르지 못합니다. 워크스페이스 설정에서 항상 켜두면 매번 확인할 필요가 없습니다.
첫 버전 테스트
완성되면 "지쳐서 아무것도 못하겠다"처럼 한 줄을 적고 버튼을 누릅니다. 메트 API가 연결됐으면 첫 방에 그림이 걸립니다.
방 넘기기와 저장 확인
옆으로 넘겨 방 2·3을 확인하고, 상세 화면에서 저장을 누른 뒤 이메일로 받은 인증 코드로 로그인해 내 미술관에 걸리는지 봅니다.
같은 프롬프트라도 온스페이스가 매번 같은 코드를 만들지는 않습니다. 영상에서도 첫 생성본은 그림을 보여주는 방식이 이전 시도보다 더 좋게 나왔고, 대신 방 2·3이 늦게 나타났습니다. 결과가 다르면 처음부터 다시 만들기보다 왼쪽 대화창에서 그 부분만 고쳐달라고 말하는 쪽이 빠릅니다.
마스터 프롬프트 전문
영상에서 넣은 프롬프트 그대로입니다. 영어로 쓴 이유는 온스페이스가 만드는 코드와 화면 규칙을 한 단어씩 정확히 지정하기 위해서입니다. 화면에 보이는 한국어 문구는 프롬프트 안에 따옴표로 박아두었기 때문에 그대로 나옵니다. 이 버전에는 영상 촬영 뒤 실제 실행에서 잡은 수정 두 가지(각 방의 시차 레이어 클리핑, 이미 공개된 방의 첫 프레임 초기값)가 반영되어 있습니다.
Build "마음미술관 (My Mood Museum)", a mobile-first Expo app for a portrait phone (design 390 x 844, scale by ratios) that must also run unchanged in the web preview. Build everything below in ONE pass, in the order given, then self-check against the checklist at the end.
== HARD RULES ==
1. Stack: React Native core (View, Text, Pressable, TextInput, Animated, Easing, FlatList, ScrollView, Modal, Linking, Platform, KeyboardAvoidingView) + expo-router single screen app/index.tsx + expo-image (pictures) + expo-sensors DeviceMotion (tilt). Nothing else. Never import three, expo-gl, skia, svg, reanimated, lottie, gesture-handler.
2. Never edit babel.config.js, metro.config.js, app.json, tsconfig, or package.json (read-only). Never create config files or platform-specific .native/.web files. No "mobile only" fallback screens.
3. All helper modules under src/ (screens, museum, services, domain). Animated with useNativeDriver: true for transform/opacity; Easing.out(Easing.cubic); durations exactly as written.
4. Style: background #F4F1EC; wall #FBF9F6; floor #EDE7DF with far strip #E4DDD3; spotlight #FFF4DC; frame border #DCC7A8 with white mat #FFFFFF; blank canvas #EEE8E0; label plate #CFC6BA; accent #F2B94F; dark overlay/text #2A2622; secondary text #7A7268. Shadows opacity 0.10–0.16 radius 10–16 (elevation 4–6). Corners 14–20 px. Titles 26–30 px weight 800, body 15 px. No gradients, no emoji in UI, Korean UI text exactly as quoted.
5. No ceiling, side walls, furniture, avatar, or joystick anywhere. Depth comes only from layer parallax and a perspective-tilted floor.
== STATE (src/domain/appState.ts) ==
Types: Artwork { id, title, artist, date, imageUrl, objectUrl }; Commentary { id, connection, detail, question }; Saved = Artwork + { mood, labelKo, commentary, createdAt }.
State: { screen: 'ask'|'reveal'|'gallery'|'detail'|'wall', mood, labelKo, artworks: Artwork[], commentary: Commentary[], selectedIndex, loading, error, saved: Saved[], user }.
Actions: SUBMIT_MOOD, SEARCH_DONE, SEARCH_FAILED, REVEAL_DONE, OPEN_DETAIL, CLOSE_DETAIL, OPEN_WALL, CLOSE_WALL, ASK_AGAIN, SET_COMMENTARY, SET_SAVED, SET_USER.
== SCREEN 1: ASK (src/screens/AskScreen.tsx) ==
Full screen. Eyebrow "MY MOOD MUSEUM" (12 px, letterSpacing 3, #7A7268) at 14% height; title "오늘 마음은\n어떤가요?" (30 px, 800); a white input card (radius 20, padding 20, min height 140, shadow) with multiline TextInput placeholder "요즘 지쳐서 아무것도 하기 싫어요"; full-width button "내 마음에 맞는 명화 찾기" (height 56, radius 18, #2A2622, white text) above the keyboard. Empty/whitespace input shows "마음을 한 줄만 적어 주세요" and does not submit. Living background: Animated backgroundColor (this is the only useNativeDriver:false animation) moving over 600 ms between #F4F1EC (0 chars), #EEF1F6 (1–20), #F6EEF0 (21+).
== DATA FLOW on submit ==
a) AI call 1 (OnSpace built-in AI, no third-party key): convert the Korean mood to strict JSON { "query", "labelKo" }; query must be one of: hope, calm, solitude, nature, night, sea, flowers; labelKo a short Korean label like "회복과 고요". Invalid output or word outside the allowlist → { query: "nature", labelKo: "자연" }. Enforce the allowlist in code.
b) The Met Collection API (https://metmuseum.github.io/, no key): GET https://collectionapi.metmuseum.org/public/collection/v1/search?hasImages=true&q=<query> → objectIDs. Take at most the first 12; fetch /public/collection/v1/objects/<id> with max 3 in flight; skip 404/errors one by one; keep only isPublicDomain === true with non-empty primaryImageSmall (fallback primaryImage); dedupe by objectID; first 3 valid. Map: id=objectID, title, artist=artistDisplayName || "작가 미상", date=objectDate, imageUrl=primaryImageSmall || primaryImage, objectUrl=objectURL. Fewer than 3 → SEARCH_FAILED("작품을 충분히 찾지 못했어요. 다시 시도해 주세요."). Prefetch the 3 images with expo-image Image.prefetch on SEARCH_DONE. Never load primaryImage into a view.
c) AI call 2, in parallel with prefetch, once for all three: input = mood + per artwork only { id, title, artist, date }; output strict JSON array of { id, connection, detail, question } in Korean (connection: how it relates to the mood, 1–2 sentences; detail: one visual element to look at; question: one reflective question). It must not invent artwork facts. Match by id; missing id → "해설을 준비하지 못했어요". Commentary never blocks the reveal (detail shows "해설을 불러오는 중" until it arrives).
== SCREEN 2: GALLERY with rooms (src/screens/GalleryScreen.tsx, src/museum/RoomPage.tsx, src/museum/layout.ts) ==
Layout for window W x H: wallHeight = 0.58H; frameWidth = 0.68W; frameHeight = frameWidth * 1.25; frameLeft = (W - frameWidth)/2; frameTop = 0.12H; wall spotlight ellipse 0.95W x 0.5H at top 0.04H; floor spotlight ellipse 0.6W x 0.12H at top 0.62H.
Header: left "다시 묻기" (ASK_AGAIN), center "오늘의 주제 · <labelKo>", right "내 미술관" (OPEN_WALL) with a badge "♥ <count>" when saved.length > 0.
Body: Animated.FlatList of 3 RoomPage items, horizontal, pagingEnabled, getItemLayout fixed, onScroll → scrollX (useNativeDriver true). Footer: 3 pagination dots (#2A2622 active, #CFC6BA inactive) and a hint "옆으로 넘겨 다음 방으로" that disappears after the first swipe. Web: ArrowLeft/ArrowRight page the list (window keydown in useEffect, Platform.OS === 'web'); focus the page on first click.
RoomPage root: a View sized exactly W x H with overflow: 'hidden'. Every parallax layer is a child of this root, so a lagging wall/spotlight/floor is clipped at its own room's edge and never bleeds into the neighbouring room (without this clip, rooms 1–2 show a wall shifted off to one side with an empty strip next to the frame while still off-screen). RoomPage layers (absolute children of that root, in order, with parallax offset = scrollX - index*W and translateX = offset * (1 - factor)): back wall (zIndex 1, factor 0.30); wall spotlight ellipse #FFF4DC opacity 0.9 (zIndex 2, factor 0.55); floor container from 0.58H to bottom, overflow hidden (zIndex 1, factor 0.70) containing an inner 130%-wide floor with a 28 px far strip, tilted with transform [{perspective:900},{translateY:-h/2},{rotateX:'30deg'},{translateY:h/2}] so the far edge stays on the 0.58H line, plus the floor spotlight ellipse #F7F3EE; frame (zIndex 3, factor 1.00): outer #DCC7A8 padding 14 radius 12 shadow → mat #FFFFFF padding 10 → canvas #EEE8E0 holding an expo-image Image contentFit "contain" (300 ms fade); label plate 120 x 14 #CFC6BA; caption: title (15 px, 700) and "artist · date" (13 px, #7A7268) centered under the frame. The frame is a Pressable → OPEN_DETAIL(index).
== CINEMATIC REVEAL (screen 'reveal' renders the gallery locked on room 0) ==
Each RoomPage receives a prop startFullyRevealed: boolean. Hidden start (startFullyRevealed false): dim overlay #2A2622 opacity 0.6 (zIndex 5, pointerEvents none after reveal), spotlight opacity 0, frame translateY -360 opacity 0, picture opacity 0, caption opacity 0. Revealed start (startFullyRevealed true): dim 0, spotlight 1, frame translateY 0 opacity 1, picture 1, caption 1. Every Animated.Value MUST be constructed with its initial value derived from startFullyRevealed at useRef time, e.g. const fin = startFullyRevealed; useRef(new Animated.Value(fin ? 0 : 0.6)).current for the dim, useRef(new Animated.Value(fin ? 1 : 0)).current for picture/spotlight/caption. Never construct every value as hidden and then fix it in a useEffect: useRef only applies once at mount and the effect runs a frame later, so a room mounted already-revealed would flash a black overlay for at least one frame. play() = Animated.sequence: (1) room 0 only: centered white text "당신의 마음을 읽는 중..." fades in 300 ms and the room waits here while loading is true, switching to "<labelKo>의 방을 여는 중..." once labelKo is known; (2) spotlight opacity 0→1 in 500 ms; (3) frame translateY -360→0 and opacity 0→1 in 650 ms; (4) picture opacity 0→1 in 400 ms; (5) caption 0→1 in 300 ms while dim 0.6→0 in 500 ms; (6) frame border flashes #F2B94F for 300 ms. When room 0 finishes → REVEAL_DONE and scrolling unlocks (scrollEnabled false until then). Rooms 1 and 2 play once when they first become ≥60% visible (onViewableItemsChanged). SEARCH_FAILED: fade the dim out and show a centered card with the message and a button "다시 시도" (ASK_AGAIN).
== TILT (src/museum/useTilt.ts) ==
Returns Animated tiltX, tiltY in about -1..1, spring-smoothed (stiffness 120, damping 18). Native: expo-sensors DeviceMotion; if isAvailableAsync() is false do nothing; setUpdateInterval(50); x = clamp(rotation.gamma/0.6,-1,1), y = clamp((rotation.beta-0.8)/0.6,-1,1); subscribe only while screen is gallery or detail; unsubscribe otherwise and on unmount. Web: window mousemove, x = clientX/innerWidth*2-1, y = clientY/innerHeight*2-1. Apply as extra translate: wall 6 px, wall spotlight 10 px, floor 8 px, frame 16 px, picture inside the frame +4 px.
== SCREEN 3: DETAIL (src/screens/DetailScreen.tsx) ==
Full-screen #2A2622. Picture (expo-image contain, full width, top 58%) with Ken Burns scale 1.0→1.06 over 12 s looping back and forth, plus tilt 10 px. Open: picture fades in 350 ms, card slides up 400 ms. Card (white, radius 20, bottom 42%): title (20 px, 800), "artist · date" (14 px, #7A7268), then three lines appearing 500 ms apart (fade + 8 px rise): connection, detail, question. Row: link "The Met에서 보기" (Linking.openURL(objectUrl)) and button "내 미술관에 저장하기". "닫기" top-left and Android hardware back → CLOSE_DETAIL back to the same room.
== AUTH + SAVE + WALL ==
Email OTP login via OnSpace Cloud, triggered only by "내 미술관에 저장하기" while logged out (return to the card and complete the save after login) or by the wall's empty-state button "로그인하고 시작하기". Table saved_artworks: user_id, met_object_id, title, artist, date, image_url, object_url, mood_text, label_ko, commentary_json, created_at; unique (user_id, met_object_id); duplicate save shows "이미 내 미술관에 있어요"; never store image bytes. src/services/favorites.ts: save(...) and listMine() ordered by created_at desc; call listMine() on start and after login → SET_SAVED.
SCREEN 4: WALL (src/screens/WallScreen.tsx): vertical ScrollView on #FBF9F6 with a thin floor strip at the bottom; saved artworks hang as frames (same frame style, 46% width) alternating left/right with title, "artist · date", the mood in quotes (13 px, #7A7268), and the saved date. Right after a save the newest frame drops in from -200 px over 600 ms. Empty state: one empty frame with "아직 걸린 작품이 없어요". Back button → CLOSE_WALL; logout button at the top.
== SELF-CHECK before you finish (answer each with DONE/NOT DONE and one line of evidence) ==
1. No forbidden imports; no config files touched; no platform-specific files.
2. Ask screen validates empty input and animates the background.
3. Allowlist enforced in code with the nature fallback.
4. Met filter: ≤12 candidates, ≤3 concurrent, 404 skipped, public domain only, primaryImageSmall only, 3 distinct works.
5. Reveal durations exactly as written; room 0 waits for the API; rooms 1–2 reveal on first view; swipe locked until room 0 finishes.
6. Parallax factors 0.30/0.55/0.70/1.00 and the floor's far edge stays on the 0.58H line.
6a. RoomPage root has overflow: 'hidden' and all parallax layers are its children: swipe to room 1 and 2, then check that the wall, spotlight and floor sit centered behind the frame with no empty strip, and nothing from one room is visible inside another.
6b. Animated initial values come from startFullyRevealed at useRef time: a room mounted with startFullyRevealed true shows no dark frame on its first render (no 0.6 dim, no hidden picture), and a hidden room still starts at dim 0.6 / picture 0 before play().
7. Tilt works on device and mouse on web; no subscription on ask/wall screens.
8. Detail: Ken Burns, staggered commentary lines, link, save, close and Android back.
9. Save requires login, duplicates blocked, wall restores after logout/login, newest frame drops in.
10. Same code runs in the web preview and on a real phone; frames fully visible on 390 x 844.
Then list every file you created and every package that was installed.프롬프트가 잠가둔 규칙과 이유
프롬프트가 긴 이유는 화면 설명 때문이 아니라 규칙 때문입니다. 온스페이스가 임의로 고르면 빌드가 깨지거나 결과가 흔들리는 지점을 미리 잠가두었습니다. 직접 고쳐 쓸 때 어디를 건드리면 안 되는지 알아두면 편합니다.
| 규칙 | 프롬프트 문구 | 이유 |
|---|---|---|
| 스택 고정 | React Native 기본 + expo-router + expo-image + expo-sensors만. three, reanimated, skia 등 금지 | 앞선 3D 시도에서 자동 설치된 three 최신 버전이 온스페이스 빌드를 반복해서 깨뜨렸습니다. 2.5D 시차만으로 깊이를 내면 라이브러리가 필요 없습니다 |
| 설정 파일 읽기 전용 | babel, metro, app.json, package.json 수정 금지 | 온스페이스는 설정 트릭으로 오류를 우회하려는 경향이 있어서, 그 길을 처음부터 막습니다 |
| 검색어 허용 목록 | hope, calm, solitude, nature, night, sea, flowers 중 하나. 벗어나면 nature | AI가 아무 단어나 고르면 메트 검색이 비거나 엉뚱한 작품이 나옵니다. 일곱 단어는 전부 이미지 있는 결과가 넉넉한 검색어입니다 |
| 메트 필터 | 최대 12개 후보, 동시 3개 조회, 404 건너뛰기, 공개 도메인만, 작은 이미지만, 서로 다른 3점 | 초당 80회 제한을 지키고, 이미지 없는 작품과 저작권 작품을 걸러내며, 원본 대형 이미지로 휴대폰 메모리를 터뜨리지 않기 위해서입니다 |
| 연출 시간 고정 | 스포트라이트 500ms, 액자 하강 650ms, 그림 400ms, 캡션 300ms | 숫자를 비워두면 매번 다른 속도가 나옵니다. 영상과 같은 느낌을 내려면 고정해야 합니다 |
| 각 방 클리핑 | RoomPage 루트에 overflow hidden, 모든 시차 레이어는 그 자식 | 없으면 아직 화면 밖에 있는 방 2·3의 벽이 한쪽으로 밀려 액자 옆이 비고 옆 방으로 새어 나옵니다 |
| 초기값 규칙 | startFullyRevealed 값으로 Animated 초기값을 생성 시점에 결정 | 전부 숨김으로 만들고 나중에 고치면, 이미 공개된 방이 최소 한 프레임 검게 덮입니다 |
| 자기 점검 | 마지막에 12개 항목을 DONE/NOT DONE으로 답하게 함 | 온스페이스가 빠뜨린 부분을 스스로 찾게 만드는 장치입니다. 첫 응답에서 NOT DONE이 있으면 그 항목만 다시 시키면 됩니다 |
질문 문구, 버튼 이름, 색상 코드, 연출 시간은 마음대로 바꿔도 됩니다. 반대로 하드 룰 다섯 개와 메트 필터, 검색어 허용 목록은 그대로 두는 편이 안전합니다. 검색어를 추가하고 싶다면 메트 사이트에서 그 단어로 먼저 검색해 이미지 있는 결과가 충분한지 확인한 뒤 넣습니다.
자주 막히는 부분
영상 촬영과 그 뒤 실행에서 실제로 걸렸던 지점입니다. 대부분 왼쪽 대화창에 한두 문장으로 고쳐달라고 말하면 해결됩니다.
방 2·3이 늦게 나타납니다. 영상에서 옆으로 넘겼을 때 두 번째, 세 번째 방이 바로 안 보였는데, 온스페이스에 물어보니 처음 보일 때 공개 연출을 다시 재생하는 의도된 동작이었습니다. 프롬프트가 그렇게 요구하기 때문입니다. 연출이 마음에 들면 그대로 두고, 바로 보이길 원하면 방 2·3은 공개된 상태로 시작하라고 말하면 됩니다.
같은 작품이 반복해서 나옵니다. "앱 서비스를 출시해서 거상이 되고 싶다"처럼 허용 목록과 거리가 먼 문장을 넣으면 AI가 같은 검색어로 떨어지고, 메트 검색 결과의 앞쪽 작품이 매번 걸립니다. 후보를 12개보다 넓게 잡고 그중에서 무작위로 고르게 하거나, 이미 보여준 작품 번호를 제외하라고 요청하면 됩니다.
벽이 한쪽으로 밀리고 액자 옆이 빕니다. 각 방의 시차 레이어가 자기 방 안에서 잘리지 않을 때 생깁니다. 이 글의 프롬프트에는 이미 수정이 들어가 있고, 이전 버전으로 만들었다면 RoomPage 루트 View에 overflow hidden을 넣고 레이어를 그 자식으로 옮기라고 말하면 됩니다.
첫 프레임에 검은 화면이 한 번 스칩니다. 이미 공개된 방을 열 때 어둡게 덮였다가 풀리는 현상입니다. 애니메이션 초기값을 useEffect에서 나중에 고치는 코드가 원인이고, 생성 시점에 startFullyRevealed 값으로 초기값을 정하라고 요청하면 사라집니다. 이 버전 프롬프트에는 그 규칙이 들어 있습니다.
갤러리가 이상해요. 다시 만들어주세요.
방 2와 3으로 넘기면 벽이 왼쪽으로 밀려 액자 오른쪽이 비어요. RoomPage 루트 View에 overflow: 'hidden'을 넣고 벽·스포트라이트·바닥·액자를 전부 그 자식으로 두세요. 다른 화면은 건드리지 마세요.
배포와 결제
프로토타입이 마음에 들면 온스페이스 안에서 바로 스토어 제출로 넘어갈 수 있습니다. 영상에서는 메뉴만 확인했고, 실제 제출과 결제 설정은 진행하지 않았습니다. 아래는 2026년 9월 9일 공식 문서 기준입니다.
앱스토어 제출
프로젝트 오른쪽 위 Publish 버튼을 누르면 앱 아이콘(PNG), 앱 이름, 버전을 넣는 팝업이 뜹니다. 다음 단계에서 애플 개발자 계정이 필요합니다. Apple Developer Program은 1년에 99달러입니다. 온스페이스는 애플 계정 정보를 서버에 저장하지 않고 내 컴퓨터에만 둔다고 밝히고 있습니다. 제출 서비스에는 1,000크레딧이 들고, 결제 뒤 애플 2단계 인증 코드를 넣으면 빌드가 몇 분 안에 끝납니다. 진행 상태는 Store Submissions 대시보드에서 Building, Submitting, Submitted, Failed로 볼 수 있습니다.
안드로이드는 다릅니다. 공식 문서는 구글 플레이 직접 제출을 지원하지 않는다고 적고 있고, Expo 문서로 안내합니다. 영상에서 구글 플레이 배포를 누르면 정리된 문서 페이지로 이동한 것이 그 경로입니다. Pro 플랜에서는 APK 다운로드가 가능하니 그 파일로 직접 올리면 됩니다.
RevenueCat으로 정기 구독 결제
이 앱에는 한 달에 몇 번까지 무료, 그 뒤는 구독자만 명화를 볼 수 있는 정기 구독이 어울립니다. 온스페이스는 RevenueCat 연동을 지원합니다. RevenueCat은 사용자가 실제로 결제했는지 검수해주는 플랫폼이라서 PG사 계약 없이 스토어 결제를 받을 수 있습니다.
다만 영상에서 테스트 키로 연결했을 때 "구독 플랜을 불러오지 못했다"는 안내가 뜬 데는 이유가 있습니다. 공식 문서 기준으로 RevenueCat 연동 전제 조건은 다음과 같습니다.
RevenueCat 연동 전제 조건
0/5 완료
앱을 출시하기 전에는 플랜이 비어 있는 것이 정상입니다. 출시한 앱의 ID를 RevenueCat에 연결하고 가격을 설정하면 페이월이 실제 플랜을 불러옵니다.
요금제
영상에서는 라이트, 플러스, 맥스 세 단계로 소개했고, 앱을 만져보고 오류를 고쳐 결과물을 내기까지는 중간 단계, 제대로 만들 사람은 최상위 단계를 권했습니다. 연간 결제 할인이 있으니 월간과 연간을 비교해보면 됩니다.
같은 날 확인한 공식 요금 페이지에는 플랜 이름이 다르게 표시됩니다. 결제 화면에 뜨는 이름과 크레딧을 기준으로 고르면 됩니다.
| 플랜 | 가격 | 크레딧 | 주요 조건 |
|---|---|---|---|
| Free | 무료 | 하루 500, 월 최대 2,000 | 공개 프로젝트, 핵심 AI 기능, iOS·안드로이드·웹 빌드 |
| Pro | 월 25달러부터 | 월 7,500부터 구간별 | 고급 코딩 모델, 비공개 프로젝트, 배지 제거, 커스텀 도메인, APK 다운로드, 크레딧 이월 |
| Enterprise | 문의 | 별도 | 학습 데이터 기본 제외, 우선 처리, SSO |
공식 문서의 요금 페이지에는 Pro가 월 7,500크레딧 25달러부터 480,000크레딧 1,299달러까지 아홉 구간으로 나뉘어 있고, 연간 결제는 각 구간의 12개월 금액보다 낮게 책정되어 있습니다. 이 앱 하나를 만들고 몇 번 고치는 정도라면 첫 구간으로 충분합니다.
FAQ
코딩을 몰라도 되나요?
메트로폴리탄 미술관 API를 상업용 앱에 써도 되나요?
AI 기능을 쓰려면 API 키가 필요한가요?
왜 검색어를 일곱 개로 제한하나요?
3D 미술관은 안 되나요?
구글 플레이에도 올릴 수 있나요?
마지막 정리입니다. 재료는 메트 API와 온스페이스 둘뿐이고, 프롬프트 한 개를 넣으면 API 연결, AI 해석, 로그인, 저장까지 붙은 앱이 나옵니다. 공개 API는 찾아보면 정말 많습니다. 다른 API로 같은 구조를 다시 쓰면 또 하나의 앱이 됩니다.

힉스필드 App Builder 가이드 — 코딩 없이 앱 만들고 10만 달러 콘테스트 출품하기
코딩을 몰라도 힉스필드 App Builder에서 아이디어를 찾고, 실제 앱을 만들고, 이름·아이콘·썸네일까지 다듬어 공개하는 전 과정을 프롬프트 3개로 정리했습니다.
그록 4.6으로 게임, 영상, 3D, 웹사이트 만들기: 4개 프로젝트 재현 가이드
그록 4.6으로 슈팅 게임, 클레이 콘티 실사 영상, 3D 우주선 사이트, 스크롤 시네마 사이트를 만들었습니다. 네 프로젝트 전부 공식은 하나입니다. 소재는 그록 이매진이나 힉스필드로 뽑고, 코드는 그록에게 짜게 합니다. 실제로 쓴 재현 프롬프트를 전문 그대로 공개합니다.
7:15Replit과 클로드 페이블 5로 코딩 없이 AR 얼굴인식 카메라 앱 만들기
Replit 파워 모드와 클로드 페이블 5로 코딩 없이 AR 얼굴인식 카메라 앱을 만드는 전 과정을 정리했습니다. 얼굴을 따라오는 가면, 손가락 개수에 따라 바뀌는 화면 이펙트, 구글 밋 스타일 가상 배경까지 프롬프트 4번 복사-붙여넣기로 완성합니다. 영상에서 사용한 전체 프롬프트를 그대로 제공합니다.
