diff --git a/404.html b/404.html index 5f44e0a..3ba0eff 100644 --- a/404.html +++ b/404.html @@ -38,11 +38,10 @@

404: page not found

This page does not exist on 207studio.dev. -- All products: https://207studio.dev/products/ - Product list for AI agents: https://207studio.dev/llms.txt - Full reference with prices and buy links: https://207studio.dev/llms-full.txt - Contact: support@207studio.dev - + diff --git a/_blog/play-pptx-in-browser.md b/_blog/play-pptx-in-browser.md index ba7c98b..1e11d66 100644 --- a/_blog/play-pptx-in-browser.md +++ b/_blog/play-pptx-in-browser.md @@ -78,8 +78,6 @@ Free options exist and they are real. [`@web-ppt/viewer-core`](https://www.npmjs Pick a free library when you have someone who can maintain it. A viewer is a moving target: PowerPoint adds effects, and every fix lands in your fork. Pick a one-time source licence when you would rather own the engine than the maintenance. The $999 figure is Aspose's own starting price, so check their current tier list before you put it in a budget. -[PPTX Player SDK](https://207studio.dev/products/pptx-player-sdk/) is our client-side engine. It renders a deck to HTML and SVG and replays entrance, exit and emphasis effects, morph transitions and embedded media, offline, in a normal browser or a WebView. Full source is included, and the licence is one-time: $14 for one developer shipping it inside their own apps, $129 for a company where more than one person uses it, with priority email support. Neither tier lets you redistribute the source itself. - ## FAQ **Can you play a PowerPoint file in a browser with its animations?** @@ -96,10 +94,3 @@ A deck with a stray `&` in the text is repaired and plays normally. If the XML i **Are the free libraries enough?** They can be, if you are ready to maintain a fork. Check the animation and morph coverage in each README first, and test them with the five item deck above before you commit. - -**Which licence tier do I need?** -Developer at $14 is one developer, and you can ship the player inside your own paid or free apps as bundled code. Commercial at $129 is for a company or organisation where more than one person uses it, and adds priority email support. Both include the full source. Neither lets you share the source itself. - ---- - -If you want to skip the fork, [start with the demo](https://207studio.dev/products/pptx-player-sdk/) and run the five minute test on the deck you already have. The other [developer kits and classroom tools](https://207studio.dev/products/) we sell work the same way: one-time purchase, source included, no subscription. diff --git a/_blog/posts.json b/_blog/posts.json index d9399f6..1ac68c4 100644 --- a/_blog/posts.json +++ b/_blog/posts.json @@ -14,7 +14,7 @@ "image": "/assets/products/pptx-player-sdk-1200.jpg", "image_width": 1200, "image_height": 675, - "image_alt": "PPTX Player SDK diagram: a slide stepped with click 1, click 2, then a morph transition", + "image_alt": "Diagram: a slide stepped with click 1, click 2, then a morph transition", "image_caption": "A deck steps the way PowerPoint plays it: click 1, click 2, then a morph transition between slides." } ] diff --git a/_products/apps.json b/_products/apps.json new file mode 100644 index 0000000..1f3e758 --- /dev/null +++ b/_products/apps.json @@ -0,0 +1,96 @@ +{ + "_readme": "The seven apps on the home page walk, in order. Run python3 _products/walk-gen.py after editing. Each app's name sits on one line (en + ko), so a rename is a one-line change. A link may add href_ko for the Korean page. The download key names an entry in site-data.json downloads (URL, SHA-256, requirements). Facts come only from the repo, the site and the App Store. Anything marked TODO still needs facts from the owners. say is Mimi's one line for the app. icon (168 px webp) and shot (src without the -1200/-720.webp suffix, its size and alt) are real app images; an app without them shows text only.", + "apps": [ + { + "id": "pairy", + "name": {"en": "Pairy", "ko": "Pairy"}, + "tag": {"en": "On the App Store", "ko": "App Store"}, + "line": {"en": "A live Mac screen on your iPad, with touch, keyboard and clipboard going back.", "ko": "Mac 화면을 iPad에 띄우고, 터치와 키보드, 클립보드를 다시 Mac으로 보냅니다."}, + "say": {"en": "This is Pairy. Your Mac screen, live on your iPad.", "ko": "Pairy예요. Mac 화면이 iPad에 그대로 떠요."}, + "what": {"en": "A remote desktop app for your tablet. See your computer screen and control it with touch, trackpad and keyboard.", "ko": "태블릿용 원격 데스크톱 앱이에요. 컴퓨터 화면을 보면서 터치, 트랙패드, 키보드로 조작해요."}, + "for": {"en": "iPad, iPhone and Vision Pro owners with an Apple silicon Mac.", "ko": "Apple 실리콘 Mac과 iPad, iPhone, Vision Pro를 함께 쓰는 사람."}, + "points": { + "en": ["Full HD streaming", "Touch, trackpad and keyboard input", "Encrypted connections between your devices"], + "ko": ["Full HD 스트리밍", "터치, 트랙패드, 키보드 입력", "기기 사이 암호화 연결"] + }, + "icon": "/assets/apps/pairy/icon-168.webp", + "shot": {"src": "/assets/apps/pairy/remote", "w": 1200, "h": 690, "alt": {"en": "Pairy on an iPad showing a Mac desktop, with an iPhone beside it as the remote", "ko": "iPad에 Mac 화면을 띄운 Pairy, 옆의 iPhone은 리모컨"}}, + "page": {"href": "/pairy/", "href_ko": "/ko/pairy/", "en": "More about Pairy", "ko": "Pairy 자세히 보기"}, + "download": "pairy-mac", + "link": {"href": "https://apps.apple.com/app/id6815584225", "en": "App Store", "ko": "App Store"} + }, + { + "id": "chalkieboard", + "name": {"en": "Chalkieboard", "ko": "초키보드"}, + "tag": {"en": "Beta (TestFlight) · iPad", "ko": "베타 (TestFlight) · iPad"}, + "line": {"en": "A lesson board app for teachers on iPad.", "ko": "선생님을 위한 아이패드 수업 판서 앱."}, + "say": {"en": "Chalkieboard is a lesson board for teachers on iPad.", "ko": "초키보드는 선생님을 위한 iPad 판서 앱이에요."}, + "icon": "/assets/apps/chalkieboard/icon-168.webp", + "shot": {"src": "/assets/apps/chalkieboard/home", "w": 1200, "h": 690, "alt": {"en": "Chalkieboard's lesson home on iPad: start a lesson, the timetable, a timer, a student picker and the clock", "ko": "iPad의 초키보드 수업 홈: 수업 시작, 시간표, 타이머, 학생 뽑기, 시계"}}, + "what": {"en": "An all-in-one board and lesson app for elementary classes. It is built to cover a teacher's whole lesson day in one app. It collects no personal data during class.", "ko": "초등 수업을 위한 올인원 판서·수업 앱이에요. 선생님의 하루 수업을 앱 하나로 해낼 수 있게 만들었어요. 수업 중에는 개인정보를 수집하지 않아요."}, + "for": {"en": "elementary school teachers who teach with an iPad.", "ko": "아이패드로 수업하는 초등 선생님."}, + "points": { + "en": ["Handwriting on the board, plus subject tools", "Open lesson files, including PPTX and HWP, right in the app", "Send the board to the classroom display with AirPlay"], + "ko": ["판서와 교과별 도구", "PPTX, HWP 같은 수업 파일을 앱에서 바로 열기", "AirPlay로 교실 화면에 판서 띄우기"] + }, + "link": {"href": "https://chalkieboard.207studio.dev/", "href_ko": "https://chalkieboard.207studio.dev/ko/", "en": "Chalkieboard site", "ko": "초키보드 사이트"} + }, + { + "id": "checkyboard", + "name": {"en": "CheckyBoard", "ko": "체키보드"}, + "tag": {"en": "In development · Windows", "ko": "개발 중 · Windows"}, + "line": {"en": "Check paper exams one question at a time.", "ko": "종이 시험을 문항별로 모아 보며 채점해요."}, + "say": {"en": "CheckyBoard checks paper exams one question at a time.", "ko": "체키보드로 종이 시험을 문항별로 채점해요."}, + "what": {"en": "A Windows app for checking paper exams one question at a time. You mark each answer right or wrong. The app handles the work around it.", "ko": "종이 시험을 문항별로 모아 보며 O/X로 체크하는 Windows 앱이에요. 판정은 사람이 하고, 그 앞뒤 일은 프로그램이 맡아요."}, + "for": {"en": "teachers who grade paper exams.", "ko": "종이 시험을 채점하는 선생님."}, + "points": { + "en": ["Make answer sheets and split scans into a box per student and question", "Mark one question across the whole class, then move on", "Scores, item analysis and report cards"], + "ko": ["답안지를 만들고, 스캔본을 학생별·문항별 칸으로 나눠요", "한 문항을 반 전체에서 이어서 채점해요", "점수, 문항 분석, 성적 통지표"] + } + }, + { + "id": "kiwi", + "name": {"en": "Cool Kiwi", "ko": "쿨 키위"}, + "_name_note": "Working name, not final. Rename here (one line), then run walk-gen.py.", + "tag": {"en": "Messenger · working name", "ko": "메신저 · 가칭"}, + "line": {"en": "A messenger app. More soon.", "ko": "메신저 앱이에요. 곧 소개할게요."}, + "say": {"en": "Cool Kiwi is a messenger. More soon.", "ko": "쿨 키위는 메신저예요. 곧 더 알려 드릴게요."}, + "todo": "TODO: what it is (one line), who it is for, 3 key points, link or status." + }, + { + "id": "stonewars", + "name": {"en": "StoneWars", "ko": "스톤워즈"}, + "tag": {"en": "Coming soon · iPhone, iPad, Mac", "ko": "출시 준비 중 · iPhone, iPad, Mac"}, + "line": {"en": "Five in a row, where every stone has a character.", "ko": "돌마다 캐릭터가 있는 오목 게임."}, + "say": {"en": "StoneWars is five in a row. Every stone has a character.", "ko": "스톤워즈는 오목이에요. 돌마다 캐릭터가 있어요."}, + "what": {"en": "A five-in-a-row game on a 15×15 board in 3D. Win with exactly five in a row or ten kills.", "ko": "15×15 입체 보드에서 두는 오목 게임이에요. 정확히 다섯 줄을 만들거나 열 번 처치하면 이겨요."}, + "points": { + "en": ["Eight stone classes, each with its own skill", "Seven worlds that change the board", "Two players on one device or against the built-in AI, no account"], + "ko": ["스킬이 다른 여덟 가지 병과", "보드가 달라지는 일곱 가지 세계", "한 기기에서 둘이, 또는 내장 AI와 대전. 계정 없이"] + }, + "todo": "TODO: who it is for.", + "link": {"href": "/stonewars/", "en": "About StoneWars", "ko": "스톤워즈 소개"} + }, + { + "id": "dekiduck", + "name": {"en": "Deki Duck", "ko": "데키 덕"}, + "tag": {"en": "Coming soon", "ko": "준비 중"}, + "line": {"en": "More soon.", "ko": "곧 소개할게요."}, + "say": {"en": "Deki Duck is coming soon.", "ko": "데키 덕은 준비 중이에요."}, + "todo": "TODO: what it is (one line), who it is for, 3 key points, link or status. Unconfirmed lead: the decky-duck repo name." + }, + { + "id": "ttoryeot", + "name": {"en": "Ttoryeot", "ko": "또렷"}, + "tag": {"en": "Coming soon · Mac, iOS, Windows, web", "ko": "출시 예정 · Mac, iOS, Windows, 웹"}, + "line": {"en": "Read HWP files without 한글 installed.", "ko": "한글 프로그램 없이 HWP 문서를 열어 봐요."}, + "say": {"en": "Ttoryeot opens HWP files without 한글 installed.", "ko": "또렷은 한글 프로그램 없이 HWP 파일을 열어요."}, + "what": {"en": "Ttoryeot (또렷) is our HWP/HWPX document engine, written in Rust. It opens 한글 documents fast and renders them faithfully to PDF or images. View only, no editing.", "ko": "또렷은 Rust로 만든 HWP/HWPX 문서 엔진이에요. 한글 문서를 빠르게 열고 원본 모양 그대로 PDF나 이미지로 그려요. 보기 전용이라 편집은 안 돼요."}, + "for": {"en": "anyone who needs to read 한글 (HWP) files without 한글 installed.", "ko": "한글 프로그램 없이 HWP 파일을 읽어야 하는 사람."}, + "points": { + "en": ["Layout checked page by page against 한글 12.30 on real documents", "Fast and light. Images in an 80-image file take 7 ms, down from 196 ms. Peak memory on a large file fell from about 1 GB to 125 MB", "One engine for Mac, iOS, Windows and the web (WASM)"], + "ko": ["실제 문서로 한글 12.30 출력과 쪽마다 대조한 레이아웃", "빠르고 가벼워요. 이미지 80장 문서는 196ms에서 7ms로, 큰 문서의 최대 메모리는 약 1GB에서 125MB로 줄었어요", "Mac, iOS, Windows, 웹(WASM)을 엔진 하나로"] + } + } + ] +} diff --git a/_products/blog-gen.mjs b/_products/blog-gen.mjs index 8d3c8ef..b3ba8c0 100644 --- a/_products/blog-gen.mjs +++ b/_products/blog-gen.mjs @@ -230,10 +230,10 @@ ${twImage ? `\n` : ''}207STUDIO`; + return ``; } -const FOOT_GRID = '
207 Studio

Software for making and teaching.

'; +const FOOT_GRID = '
207 Studio

Software for making and teaching.

'; function footer(ko) { const lang = ko ? `\ud55c\uad6d\uc5b4` : ''; diff --git a/_products/gen/check_llms.py b/_products/gen/check_llms.py index f935ed1..b6912ed 100644 --- a/_products/gen/check_llms.py +++ b/_products/gen/check_llms.py @@ -2,6 +2,7 @@ Usage: python3 _products/gen/check_llms.py [--write] --write rewrites the Products block from data (keeps the rest of the file). +With no products in site-data.json, llms.txt must have no Products block. """ import json import sys @@ -10,7 +11,6 @@ ROOT = Path(__file__).resolve().parents[2] DATA = json.loads((ROOT / "_products/site-data.json").read_text(encoding="utf-8")) LLMS = ROOT / "llms.txt" -HEAD = "# 207 Studio\n\n> Developer kits and classroom tools from 207 Studio (207studio.dev), South Korea. One-time purchase, full source.\n\n## Products\n\n" def products_block() -> str: @@ -24,6 +24,13 @@ def products_block() -> str: def main() -> int: text = LLMS.read_text(encoding="utf-8") + if not DATA["products"]: + # No products on sale: llms.txt must not carry a Products block at all. + if "## Products" in text: + print("drift: site-data.json has no products but llms.txt has a Products block") + return 1 + print("ok: no products in site-data.json or llms.txt") + return 0 start = text.index("## Products") end = text.index("\n## ", start + 3) + 1 actual = text[start:end] diff --git a/_products/gen/src/main.rs b/_products/gen/src/main.rs index e297ffb..4b1cf77 100644 --- a/_products/gen/src/main.rs +++ b/_products/gen/src/main.rs @@ -14,6 +14,27 @@ struct SiteData { listing: Listing, organization: Organization, products: Vec, + #[serde(default)] + downloads: Vec, +} + +/// A file served from a GitHub Release asset (the Pairy Mac app). Listed in llms.txt and llms-full.txt. +#[derive(Deserialize)] +struct Download { + name: String, + version: String, + build: String, + platform: String, + install: Localized, + file: String, + url: String, + sha256: String, + size: u64, +} + +#[derive(Deserialize)] +struct Localized { + en: String, } #[derive(Deserialize)] @@ -166,12 +187,15 @@ fn load_sources(data_path: &Path) -> Result { } fn generate(data: &SiteData, sources: &Sources, root: &Path) -> Result<(), String> { - write_file(root, "products/index.html", &render_listing(data)?)?; - for product in &data.products { - let path = format!("products/{}/index.html", product.slug); - write_file(root, &path, &render_product(data, product)?)?; + // With no products the /products/ pages are hand-written redirect stubs, so leave them alone. + if !data.products.is_empty() { + write_file(root, "products/index.html", &render_listing(data)?)?; + for product in &data.products { + let path = format!("products/{}/index.html", product.slug); + write_file(root, &path, &render_product(data, product)?)?; + } + write_file(root, "products/products.css", CSS)?; } - write_file(root, "products/products.css", CSS)?; write_file(root, "sitemap.xml", &render_sitemap(data, &sources.posts))?; write_file(root, "robots.txt", &render_robots(data))?; write_file(root, "llms.txt", &render_llms(data, sources)?)?; @@ -788,10 +812,11 @@ fn render_sitemap(data: &SiteData, posts: &[Post]) -> String { let base = &data.base_url; let updated = data.last_updated.as_str(); let product_lastmod = |product: &Product| product.lastmod.as_deref().unwrap_or(updated).to_string(); - let mut entries: Vec<(String, String)> = vec![ - (format!("{base}/"), updated.to_string()), - (canonical_products(base), updated.to_string()), - ]; + let listed = !data.products.is_empty(); + let mut entries: Vec<(String, String)> = vec![(format!("{base}/"), updated.to_string())]; + if listed { + entries.push((canonical_products(base), updated.to_string())); + } for product in &data.products { entries.push((canonical_product(base, &product.slug), product_lastmod(product))); } @@ -800,13 +825,16 @@ fn render_sitemap(data: &SiteData, posts: &[Post]) -> String { let lastmod = post.lastmod.as_deref().unwrap_or(&post.date); entries.push((format!("{base}/blog/{}/", post.slug), lastmod.to_string())); } - for page in ["legal/terms/", "legal/privacy/", "legal/refunds/", "about/", "ko/", "ko/products/"] { + for page in ["legal/terms/", "legal/privacy/", "legal/refunds/", "about/", "pairy/", "ko/"] { entries.push((format!("{base}/{page}"), updated.to_string())); } + if listed { + entries.push((format!("{base}/ko/products/"), updated.to_string())); + } for product in &data.products { entries.push((format!("{base}/ko/products/{}/", product.slug), product_lastmod(product))); } - for page in ["ko/about/", "ko/blog/"] { + for page in ["ko/about/", "ko/blog/", "ko/pairy/"] { entries.push((format!("{base}/{page}"), updated.to_string())); } @@ -869,7 +897,19 @@ fn render_llms(data: &SiteData, sources: &Sources) -> Result { post.title, data.base_url, post.slug, post.description )); } - let out = fill(&sources.llms, "llms.txt", "{{products}}", &products)?; + // The Products heading only appears when there is something to list. + if !products.is_empty() { + products = format!("## Products\n\n{products}\n"); + } + let mut downloads = String::new(); + for d in &data.downloads { + downloads.push_str(&format!( + "- [{} {} (build {})]({}): direct download. {} {}\n", + d.name, d.version, d.build, d.url, d.platform, d.install.en + )); + } + let out = fill(&sources.llms, "llms.txt", "{{products}}\n", &products)?; + let out = fill(&out, "llms.txt", "{{downloads}}", &downloads)?; let out = fill(&out, "llms.txt", "{{writing}}", &writing)?; fill(&out, "llms.txt", "{{ko_products}}\n", &korean) } @@ -897,7 +937,15 @@ fn render_llms_full(data: &SiteData, sources: &Sources) -> Result https://207studio.dev · support@207studio.dev · South Korea. One-time purchase, full source. Commercial tiers cover companies with annual revenue up to $1M; above that, email support@207studio.dev for an Enterprise license. +> https://207studio.dev · support@207studio.dev · South Korea. Founder: Jang Yuil (장유일) {{products}}## Pairy: Connecting Devices (iOS/iPadOS app) https://apps.apple.com/app/id6815584225 - remote desktop for your tablet. Paid app, one-time purchase ($4.99 in the US). +Page: https://207studio.dev/pairy/ (how it works, screenshots, the Mac companion download) -## pptx-mcp (free) +{{downloads}}## In development -https://github.com/207studio/pptx-mcp - MCP server for opening and presenting .pptx files from an AI agent. +- [Chalkieboard](https://chalkieboard.207studio.dev/) (초키보드): an iPad lesson board app for elementary school teachers. Board writing with subject tools, PPTX and HWP lesson files opened in the app, and AirPlay to the classroom display. No personal data is collected during class. In beta on TestFlight. Support: https://chalkieboard.207studio.dev/support/ · Tester guide: https://chalkieboard.207studio.dev/beta/ +- CheckyBoard: a Windows app for checking paper exams one question at a time. The teacher marks each answer, and the app makes answer sheets, splits scans into per-question boxes, and produces scores, item analysis and report cards. +- [StoneWars](https://207studio.dev/stonewars/): five in a row on a 15×15 board in 3D for iPhone, iPad and Mac, with eight stone classes and seven worlds. Preparing for App Store release. +- Ttoryeot (또렷): an HWP/HWPX document engine written in Rust. Opens Korean 한글 documents and renders them to PDF or images without 한글 installed. View only. One engine for Mac, iOS, Windows and the web. Not released yet. ## Contact diff --git a/_products/templates/llms.txt b/_products/templates/llms.txt index 7a72f53..b19ae35 100644 --- a/_products/templates/llms.txt +++ b/_products/templates/llms.txt @@ -1,51 +1,31 @@ # 207 Studio -> Developer kits and classroom tools from 207 Studio (207studio.dev), South Korea. One-time purchase, full source. +> Apps from 207 Studio (207studio.dev), an independent software studio in South Korea. Founder: Jang Yuil (장유일) -## Products - {{products}} ## Apps -- [Pairy: Connecting Devices](https://apps.apple.com/app/id6815584225): Remote desktop for your tablet. Shows your computer's screen on your tablet and lets you control it with touch, trackpad and keyboard. Paid app, one-time purchase ($4.99 in the US). - -## Licensing +- [Pairy: Connecting Devices](https://apps.apple.com/app/id6815584225): Remote desktop for your tablet. Shows your computer's screen on your tablet and lets you control it with touch, trackpad and keyboard. Paid app, one-time purchase ($4.99 in the US). How it works and the Mac companion: https://207studio.dev/pairy/ +{{downloads}} +## In development -- Commercial licenses cover companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license. All purchases are one-time; no subscriptions. +- [Chalkieboard](https://chalkieboard.207studio.dev/) (초키보드): an iPad lesson board app for elementary school teachers. Board writing with subject tools, PPTX and HWP lesson files opened in the app, and AirPlay to the classroom display. No personal data is collected during class. In beta on TestFlight. +- CheckyBoard: a Windows app for checking paper exams one question at a time. The teacher marks each answer, and the app makes answer sheets, splits scans into per-question boxes, and produces scores, item analysis and report cards. +- [StoneWars](https://207studio.dev/stonewars/): five in a row on a 15×15 board in 3D for iPhone, iPad and Mac, with eight stone classes and seven worlds. Preparing for App Store release. +- Ttoryeot (또렷): an HWP/HWPX document engine written in Rust. Opens Korean 한글 documents and renders them to PDF or images without 한글 installed. View only. One engine for Mac, iOS, Windows and the web. Not released yet. ## Writing {{writing}} ## 한국어 -- [207스튜디오 홈](https://207studio.dev/ko/): 교실 도구와 소스 포함 개발자 키트를 1회 구매로 판매해요. -- [상품](https://207studio.dev/ko/products/): 개발자 키트와 교실 도구 전체 목록. 가격과 구매 링크가 있어요. +- [207스튜디오 홈](https://207studio.dev/ko/): 대한민국의 독립 소프트웨어 스튜디오. 앱과 도구를 만들어요. {{ko_products}} - [소개](https://207studio.dev/ko/about/): 207 Studio가 무엇을 만드는지, 어떻게 문의하는지. - [글](https://207studio.dev/ko/blog/): 브라우저 렌더링과 교실 도구에 관한 글. -## When to use 207 Studio - -- You need to play or preview .pptx files in a web page with click-by-click animations, without a server or Office: PPTX Player SDK. -- You grade paper exams and want scanned sheets turned into scores: QR Answer-Sheet Cropper, OMR Auto-Grader, or Scan-to-Grade (Python). -- You make printed exams with matching OMR sheets and keys: Exam + OMR Sheet Builder. -- You need offline voice commands that only fire in the right situation: Context-Gated Voice Commands (Rust). -- You build a Mac-to-iPad remote screen or drawing app: PairyCore SDK, ShapeFit. -- You want an AI agent to open and present PowerPoint files: the free pptx-mcp server. - -## How to buy (for agents) - -- Polar is the main checkout. Every product page lists tiers with a direct Polar link; card payment, one-time, instant download, no account needed beforehand. -- Paddle is the alternative checkout (card or PayPal): https://207studio.dev/pay/ -- Machine-readable prices and buy URLs: schema.org Product/Offer JSON-LD on each product page, and https://207studio.dev/llms-full.txt -- Questions or Enterprise quotes: support@207studio.dev - -## Free & open source - -- [pptx-mcp](https://github.com/207studio/pptx-mcp): MCP server that lets an AI agent open, inspect and present .pptx files. Free. - ## About - [About 207 Studio](https://207studio.dev/about/): who we are, where we are, how to contact us. diff --git a/_products/walk-gen.py b/_products/walk-gen.py new file mode 100644 index 0000000..8ad8993 --- /dev/null +++ b/_products/walk-gen.py @@ -0,0 +1,77 @@ +"""Render the home page walk (index.html, ko/index.html) from _products/apps.json. + +Usage: python3 _products/walk-gen.py +Rewrites the
    list. Run it after +any edit to apps.json, e.g. renaming an app. +""" +import json +import re +from html import escape +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +APPS = json.loads((ROOT / "_products/apps.json").read_text(encoding="utf-8"))["apps"] +DOWNLOADS = {d["id"]: d for d in json.loads((ROOT / "_products/site-data.json").read_text(encoding="utf-8")).get("downloads", [])} +FOR = {"en": "For", "ko": "대상"} +DL = {"en": "Download for Mac", "ko": "Mac용 다운로드"} + + +def link_attrs(href): + return f' href="{escape(href)}" target="_blank" rel="noopener"' if href.startswith("http") else f' href="{escape(href)}"' + + +def href_for(target, lang): + return target.get("href_" + lang, target["href"]) + + +def stop(app, i, lang): + """One card: a cream sheet (icon, tag, name, actions, line, screenshot) over a night band + (what it is, who it is for, points). The sheet is a grid, so CSS alone moves the actions + below the screenshot on phones.""" + n = f"{i + 1:02d}" + poster = [] + if app.get("icon"): + poster.append(f'') + poster.append(f'

    {n}{escape(app["tag"][lang])}' + f'{escape(app["name"][lang])}

    ') + acts = [] + link = app.get("link") + if link: + acts.append(f'{escape(link[lang])} ') + dl = DOWNLOADS[app["download"]] if app.get("download") else None + if dl: + acts.append(f'{DL[lang]}{escape(dl["requires"][lang])}') + if acts: + poster.append(f'

    {"".join(acts)}

    ') + poster.append(f'

    {escape(app["line"][lang])}

    ') + shot = app.get("shot") + if shot: + poster.append(f'{escape(shot[') + focus = "" if acts else ' tabindex="0"' + lines = [f'
    ', *(" " + p for p in poster), "
    "] + if app.get("what"): + note = [f'

    {escape(app["what"][lang])}

    '] + if app.get("for"): + note.append(f'

    {FOR[lang]} {escape(app["for"][lang])}

    ') + if app.get("points"): + note.append('
      ' + "".join(f"
    • {escape(p)}
    • " for p in app["points"][lang]) + "
    ") + if app.get("page"): + note.append(f'{escape(app["page"][lang])} ') + lines += ['
    ', *(" " + p for p in note), "
    "] + on = " is-on" if i == 0 else "" + inner = "".join(f" {line}\n" for line in lines) + return f'
  1. \n{inner}
  2. \n' + + +def render(path, lang): + text = path.read_text(encoding="utf-8") + items = "".join(stop(a, i, lang) for i, a in enumerate(APPS)) + text, k = re.subn(r'(
      \n).*?(\s*
    )', lambda m: m.group(1) + items.rstrip("\n") + m.group(2), text, flags=re.S) + assert k == 1, path + path.write_text(text, encoding="utf-8") + print("wrote", path.relative_to(ROOT)) + + +render(ROOT / "index.html", "en") +render(ROOT / "ko/index.html", "ko") diff --git a/about/index.html b/about/index.html index 1132cf0..1f39451 100644 --- a/about/index.html +++ b/about/index.html @@ -38,27 +38,14 @@

    About 207 Studio

    -

    207 Studio is an independent software studio in South Korea. We build practical tools for teaching, classroom presentation and assessment, and we sell the engines behind them as source-included developer kits.

    +

    207 Studio is an independent software studio in South Korea. We build practical apps and tools for teaching, classroom presentation and assessment.

    Founded by Jang Yuil (장유일).

    -

    Everything we sell is a one-time purchase with full source code. There are no subscriptions. Personal tiers are for one person; Commercial tiers cover a company or organisation with annual revenue up to $1M. Above that, we offer a separately negotiated Enterprise license.

    -

    What we sell

    - -

    We also publish Pairy: Connecting Devices on the App Store and the free pptx-mcp server for AI agents.

    +

    What we make

    +

    Pairy: Connecting Devices is on the App Store. Chalkieboard is in beta on TestFlight. CheckyBoard and StoneWars are in development.

    Contact

    Email support@207studio.dev for support, licensing and Enterprise questions. We read every message.

    Policies: Terms · Privacy · Refunds. Code: github.com/207studio.

    For AI agents: llms.txt · llms-full.txt.

    -
    + diff --git a/assets/apps/chalkieboard/home-1200.webp b/assets/apps/chalkieboard/home-1200.webp new file mode 100644 index 0000000..1c21ef3 Binary files /dev/null and b/assets/apps/chalkieboard/home-1200.webp differ diff --git a/assets/apps/chalkieboard/home-720.webp b/assets/apps/chalkieboard/home-720.webp new file mode 100644 index 0000000..3f2c4d1 Binary files /dev/null and b/assets/apps/chalkieboard/home-720.webp differ diff --git a/assets/apps/chalkieboard/icon-168.webp b/assets/apps/chalkieboard/icon-168.webp new file mode 100644 index 0000000..a7f416c Binary files /dev/null and b/assets/apps/chalkieboard/icon-168.webp differ diff --git a/assets/apps/pairy/icon-168.webp b/assets/apps/pairy/icon-168.webp new file mode 100644 index 0000000..6c1aa0c Binary files /dev/null and b/assets/apps/pairy/icon-168.webp differ diff --git a/assets/apps/pairy/remote-1200.webp b/assets/apps/pairy/remote-1200.webp new file mode 100644 index 0000000..e304100 Binary files /dev/null and b/assets/apps/pairy/remote-1200.webp differ diff --git a/assets/apps/pairy/remote-720.webp b/assets/apps/pairy/remote-720.webp new file mode 100644 index 0000000..a7c4600 Binary files /dev/null and b/assets/apps/pairy/remote-720.webp differ diff --git a/assets/apps/pairy/store-01-couch-1200.webp b/assets/apps/pairy/store-01-couch-1200.webp new file mode 100644 index 0000000..88967ca Binary files /dev/null and b/assets/apps/pairy/store-01-couch-1200.webp differ diff --git a/assets/apps/pairy/store-01-couch-640.webp b/assets/apps/pairy/store-01-couch-640.webp new file mode 100644 index 0000000..323df70 Binary files /dev/null and b/assets/apps/pairy/store-01-couch-640.webp differ diff --git a/assets/apps/pairy/store-02-remote-1200.webp b/assets/apps/pairy/store-02-remote-1200.webp new file mode 100644 index 0000000..6016645 Binary files /dev/null and b/assets/apps/pairy/store-02-remote-1200.webp differ diff --git a/assets/apps/pairy/store-02-remote-640.webp b/assets/apps/pairy/store-02-remote-640.webp new file mode 100644 index 0000000..10dc3af Binary files /dev/null and b/assets/apps/pairy/store-02-remote-640.webp differ diff --git a/assets/apps/pairy/store-03-dictation-1200.webp b/assets/apps/pairy/store-03-dictation-1200.webp new file mode 100644 index 0000000..7eaac70 Binary files /dev/null and b/assets/apps/pairy/store-03-dictation-1200.webp differ diff --git a/assets/apps/pairy/store-03-dictation-640.webp b/assets/apps/pairy/store-03-dictation-640.webp new file mode 100644 index 0000000..5b76f08 Binary files /dev/null and b/assets/apps/pairy/store-03-dictation-640.webp differ diff --git a/assets/apps/pairy/store-04-shortcuts-1200.webp b/assets/apps/pairy/store-04-shortcuts-1200.webp new file mode 100644 index 0000000..9fcd3be Binary files /dev/null and b/assets/apps/pairy/store-04-shortcuts-1200.webp differ diff --git a/assets/apps/pairy/store-04-shortcuts-640.webp b/assets/apps/pairy/store-04-shortcuts-640.webp new file mode 100644 index 0000000..9f79122 Binary files /dev/null and b/assets/apps/pairy/store-04-shortcuts-640.webp differ diff --git a/assets/apps/pairy/store-05-trackpad-1200.webp b/assets/apps/pairy/store-05-trackpad-1200.webp new file mode 100644 index 0000000..75c8323 Binary files /dev/null and b/assets/apps/pairy/store-05-trackpad-1200.webp differ diff --git a/assets/apps/pairy/store-05-trackpad-640.webp b/assets/apps/pairy/store-05-trackpad-640.webp new file mode 100644 index 0000000..e2f63fb Binary files /dev/null and b/assets/apps/pairy/store-05-trackpad-640.webp differ diff --git a/assets/apps/pairy/store-06-pick-mac-1200.webp b/assets/apps/pairy/store-06-pick-mac-1200.webp new file mode 100644 index 0000000..09b08b8 Binary files /dev/null and b/assets/apps/pairy/store-06-pick-mac-1200.webp differ diff --git a/assets/apps/pairy/store-06-pick-mac-640.webp b/assets/apps/pairy/store-06-pick-mac-640.webp new file mode 100644 index 0000000..929d918 Binary files /dev/null and b/assets/apps/pairy/store-06-pick-mac-640.webp differ diff --git a/assets/apps/pairy/store-ko-01-couch-1200.webp b/assets/apps/pairy/store-ko-01-couch-1200.webp new file mode 100644 index 0000000..77dc747 Binary files /dev/null and b/assets/apps/pairy/store-ko-01-couch-1200.webp differ diff --git a/assets/apps/pairy/store-ko-01-couch-640.webp b/assets/apps/pairy/store-ko-01-couch-640.webp new file mode 100644 index 0000000..298bb16 Binary files /dev/null and b/assets/apps/pairy/store-ko-01-couch-640.webp differ diff --git a/assets/apps/pairy/store-ko-02-remote-1200.webp b/assets/apps/pairy/store-ko-02-remote-1200.webp new file mode 100644 index 0000000..e6075b6 Binary files /dev/null and b/assets/apps/pairy/store-ko-02-remote-1200.webp differ diff --git a/assets/apps/pairy/store-ko-02-remote-640.webp b/assets/apps/pairy/store-ko-02-remote-640.webp new file mode 100644 index 0000000..f20ae13 Binary files /dev/null and b/assets/apps/pairy/store-ko-02-remote-640.webp differ diff --git a/assets/apps/pairy/store-ko-03-dictation-1200.webp b/assets/apps/pairy/store-ko-03-dictation-1200.webp new file mode 100644 index 0000000..2ea2409 Binary files /dev/null and b/assets/apps/pairy/store-ko-03-dictation-1200.webp differ diff --git a/assets/apps/pairy/store-ko-03-dictation-640.webp b/assets/apps/pairy/store-ko-03-dictation-640.webp new file mode 100644 index 0000000..a468de6 Binary files /dev/null and b/assets/apps/pairy/store-ko-03-dictation-640.webp differ diff --git a/assets/apps/pairy/store-ko-04-shortcuts-1200.webp b/assets/apps/pairy/store-ko-04-shortcuts-1200.webp new file mode 100644 index 0000000..0367a20 Binary files /dev/null and b/assets/apps/pairy/store-ko-04-shortcuts-1200.webp differ diff --git a/assets/apps/pairy/store-ko-04-shortcuts-640.webp b/assets/apps/pairy/store-ko-04-shortcuts-640.webp new file mode 100644 index 0000000..6074de5 Binary files /dev/null and b/assets/apps/pairy/store-ko-04-shortcuts-640.webp differ diff --git a/assets/apps/pairy/store-ko-05-trackpad-1200.webp b/assets/apps/pairy/store-ko-05-trackpad-1200.webp new file mode 100644 index 0000000..09a460a Binary files /dev/null and b/assets/apps/pairy/store-ko-05-trackpad-1200.webp differ diff --git a/assets/apps/pairy/store-ko-05-trackpad-640.webp b/assets/apps/pairy/store-ko-05-trackpad-640.webp new file mode 100644 index 0000000..a41c7c4 Binary files /dev/null and b/assets/apps/pairy/store-ko-05-trackpad-640.webp differ diff --git a/assets/apps/pairy/store-ko-06-pick-mac-1200.webp b/assets/apps/pairy/store-ko-06-pick-mac-1200.webp new file mode 100644 index 0000000..e25113a Binary files /dev/null and b/assets/apps/pairy/store-ko-06-pick-mac-1200.webp differ diff --git a/assets/apps/pairy/store-ko-06-pick-mac-640.webp b/assets/apps/pairy/store-ko-06-pick-mac-640.webp new file mode 100644 index 0000000..a3438cf Binary files /dev/null and b/assets/apps/pairy/store-ko-06-pick-mac-640.webp differ diff --git a/assets/fonts/instrument-sans-latin.woff2 b/assets/fonts/instrument-sans-latin.woff2 index 07823c5..9f22348 100644 Binary files a/assets/fonts/instrument-sans-latin.woff2 and b/assets/fonts/instrument-sans-latin.woff2 differ diff --git a/assets/home.css b/assets/home.css index 3ae1709..f5a45d1 100644 --- a/assets/home.css +++ b/assets/home.css @@ -34,7 +34,6 @@ src: url("/assets/fonts/instrument-sans-latin.woff2") format("woff2"); font-style: normal; font-weight: 400 700; - font-stretch: 75% 100%; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @@ -529,47 +528,6 @@ dd { line-height: 1; } -/* Mimi: a rendered clay figure layered behind the draggable pieces */ -.mimi { - position: absolute; - right: 13%; - bottom: 0; - z-index: 1; - height: 100%; - aspect-ratio: 431 / 990; - pointer-events: none; - translate: var(--mimi-x, 0) var(--mimi-y, 0); - transition: translate 0.25s var(--ease); -} - -.mimi img { - display: block; - width: 100%; - height: auto; -} - -.mimi::after { - position: absolute; - bottom: -1%; - left: 18%; - z-index: -1; - width: 70%; - height: 4.5%; - border-radius: 50%; - background: radial-gradient(closest-side, rgba(45, 36, 26, 0.22), rgba(45, 36, 26, 0)); - content: ""; -} - -@keyframes mimi-float { - 0%, 100% { transform: translateY(-1.2%); } - 50% { transform: translateY(-2.6%); } -} - -@keyframes mimi-shadow { - 0%, 100% { opacity: 1; transform: scaleX(1); } - 50% { opacity: 0.75; transform: scaleX(0.92); } -} - /* Clay scene */ .scene { position: absolute; @@ -1411,12 +1369,6 @@ html:lang(ko) .eyebrow { min-height: 600px; } - .mimi { - top: 78px; - right: 4%; - bottom: auto; - height: 320px; - } .hero-stage::before { top: -4%; @@ -1731,120 +1683,158 @@ html { touch-action: manipulation; } } } -/* Walk with Mimi. - Static (no JS, or reduced motion): heading, Mimi standing, the five products as a list. - Live (.is-live): a sticky 100svh scene. Mimi takes over from the hero Mimi at the same - figure height, body centre and feet, and walks on the spot. Behind her a wall recedes in - CSS 3D (one rotateY plane under the stage's perspective); five paper posters are pasted - on that plane, so they share its vanishing point. Walking slides them along the wall: - one arrives from the near edge, she stops beside it, then walks on while it slides away. - Wide screens: the posters hang on the wall to her left. Phones: above her head, with the - stop list on the floor below her when there is room. - Only transform and opacity change per frame; geometry is measured on resize. */ +/* Walk. + Static (no JS, reduced motion, or a short screen): heading, then the seven cards in a grid. + Live (.js, motion allowed, tall enough): a pinned stage. Mimi stands on the left and names + each app in a speech bubble; one tall card sits to her right, and the next card comes up + from depth as the current one falls back. See the block further down. */ .walk { position: relative; padding-block: clamp(64px, 9vw, 128px) 0; } .walk-head h2 { margin: 0; font-family: var(--font-display); font-size: clamp(2.25rem, 5vw, 4rem); font-weight: 700; line-height: 0.98; letter-spacing: -0.035em; text-wrap: balance; } .walk-note { max-width: 46ch; margin: var(--space-4) 0 0; color: var(--muted); font-size: 17px; line-height: 1.5; text-wrap: pretty; } .walk-scroller { position: relative; } -.walk-stage { position: relative; width: min(1240px, calc(100% - 96px)); margin: var(--space-8) auto 0; display: grid; grid-template-columns: minmax(150px, 240px) 1fr; gap: var(--space-6); align-items: start; } -.walk-track { grid-column: 2; grid-row: 1; margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--space-4); } -.walker { grid-column: 1; grid-row: 1 / span 2; position: sticky; top: 104px; } -.walker-still { display: block; width: 100%; height: auto; } -.walk-wall, .walker-layer, .walker-floor, .walk-rail { display: none; } -.walk-end { grid-column: 2; padding: var(--space-5) 0 0; } +.walk-stage { position: relative; width: min(1240px, calc(100% - 96px)); margin: var(--space-8) auto 0; } +.walk-track { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-5); align-items: start; } +.stop { display: flex; flex-direction: column; border-radius: 6px; overflow: hidden; box-shadow: 0 18px 40px rgba(4, 6, 20, 0.45); } +.walk-end { width: min(1240px, calc(100% - 96px)); margin: 0 auto; padding: var(--space-5) 0 0; } .walk-end-t { margin: 0; font-family: var(--font-display); font-size: 1.4rem; font-weight: 700; letter-spacing: -0.02em; } .walk-end-p { margin: var(--space-2) 0 var(--space-3); color: var(--muted); } -/* Posters: thin paper sheets pasted flat on the wall: a hairline edge and a strip of tape, - no lift and no shadow. Big name, one line, a link. */ -.poster { position: relative; display: flex; flex-direction: column; gap: var(--space-3); height: 100%; padding: 22px 24px 20px; border: 1px solid rgba(45, 36, 26, 0.12); border-radius: 2px; background: linear-gradient(180deg, #fdfcf8, #f8f6f0); color: inherit; text-decoration: none; isolation: isolate; } -.poster::before { position: absolute; top: -7px; left: 50%; width: 64px; height: 15px; margin-left: -32px; border-radius: 1px; background: rgba(214, 203, 178, 0.55); content: ""; transform: rotate(-1.5deg); } -/* Lit / hover: the sheet brightens (a white wash under the text) and its tape firms up, - both by opacity only. No border, no shadow. */ -.poster::before { opacity: 0.75; transition: opacity 0.4s var(--ease); } -.poster::after { position: absolute; inset: 0; z-index: -1; border-radius: inherit; background: #fffffd; opacity: 0; content: ""; pointer-events: none; transition: opacity 0.4s var(--ease); } -.poster:hover::after, .poster:focus-visible::after { opacity: 1; } -.poster:hover::before, .poster:focus-visible::before { opacity: 1; } -.poster:focus-visible { outline: 3px solid var(--accent); outline-offset: 4px; } -.poster-head, .poster-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; } +/* The card's top: a cream sheet with a strip of tape. Icon and name, the links, one line, + then the real app screen. */ +.poster { + position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); + grid-template-areas: "icon head" "acts acts" "desc desc" "shot shot"; + align-items: center; gap: 14px 16px; padding: 24px 24px 22px; + background: linear-gradient(180deg, #fdfcf8, #f6f3ea); +} +.poster:not(:has(.poster-icon)) { grid-template-columns: 0 minmax(0, 1fr); column-gap: 0; } +.poster::before { position: absolute; top: -1px; left: 50%; width: 64px; height: 15px; margin-left: -32px; border-radius: 1px; background: rgba(214, 203, 178, 0.7); content: ""; transform: rotate(-1.5deg); } +.poster:focus-visible { outline: 3px solid var(--gold); outline-offset: -3px; } +.poster-icon { grid-area: icon; width: 84px; height: 84px; border-radius: 19px; box-shadow: 0 6px 16px rgba(27, 24, 56, 0.25); } +.poster-head { grid-area: head; display: flex; flex-direction: column; gap: 4px; min-width: 0; margin: 0; } .poster-tag { display: flex; align-items: baseline; gap: 10px; color: var(--muted); font-size: 13px; font-weight: 500; } .poster-n { color: var(--accent); font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; } -.poster-name { font-family: var(--font-display); font-size: clamp(1.45rem, 2.5vw, 2.3rem); font-weight: 700; line-height: 1.04; letter-spacing: -0.03em; text-wrap: balance; } -.poster-desc { max-width: 44ch; color: var(--ink-soft); line-height: 1.45; text-wrap: pretty; } -.poster-go { color: var(--accent); font-weight: 600; } - -/* Live scene */ -.walk.is-live .walk-stage { position: sticky; top: 0; display: block; width: 100%; height: 100vh; height: 100svh; margin: 0; overflow: hidden; overflow: clip; contain: layout paint; } -.walk.is-live .walk-stage::before { position: absolute; left: calc(var(--mx) + var(--mw) * 0.5); bottom: calc(var(--mb) - 22svh); width: calc(var(--mh) * 2.1); aspect-ratio: 1; border-radius: 50%; background: radial-gradient(closest-side, var(--paper-light) 0%, rgba(251, 251, 250, 0.6) 55%, rgba(251, 251, 250, 0) 100%); content: ""; transform: translateX(-50%); pointer-events: none; } - -/* The wall and the poster track are the same plane: both are turned by rotateY about their - left edge (set by the script), seen through the stage's perspective. Seams are one - repeating stripe that slides along the plane. */ -.walk.is-live .walk-wall, .walk.is-live .walk-track { position: absolute; top: 0; left: 0; display: block; width: 0; height: 0; margin: 0; padding: 0; transform-origin: 0 0; pointer-events: none; } -.walk.is-live .walk-wall { z-index: 0; } -.walk.is-live .walk-track { z-index: 1; } -.walk.is-live .wall-face { position: absolute; background: linear-gradient(180deg, rgba(232, 231, 226, 0.35), rgba(232, 231, 226, 0.85) 70%, rgba(226, 224, 218, 0.95)); } -.walk.is-live .wall-ledge { position: absolute; background: rgba(45, 36, 26, 0.16); } -.walk.is-live .wall-seams { position: absolute; display: block; background: linear-gradient(90deg, rgba(45, 36, 26, 0.12) 0 1px, transparent 1px) 0 0 / 200px 100% repeat-x; will-change: transform; } - -.walk.is-live .stop { position: absolute; top: 0; left: 0; width: 520px; opacity: 0; pointer-events: none; transform-origin: 0 0; will-change: transform, opacity; } -.walk.is-live .stop.is-on { pointer-events: auto; } -.walk.is-live .poster { height: auto; gap: 16px; padding: 34px 40px 30px; background: linear-gradient(180deg, #f8f6ef, #f1eee5); } -.walk.is-live .poster-head { gap: 10px; } -.walk.is-live .poster-name { font-size: clamp(2.2rem, 3.9vw, 3.6rem); } -.walk.is-live .poster-desc { max-width: 30ch; font-size: clamp(19px, 1.6vw, 23px); } -.walk.is-live .poster-go { font-size: 17px; } -.walk.is-live .stop.is-here .poster::after, .walk.is-live .stop.is-here .poster::before { opacity: 1; } - -.walk.is-live .walker { position: absolute; top: auto; left: var(--mx); bottom: var(--mb); z-index: 3; width: var(--mw); height: var(--mh); opacity: 0; pointer-events: none; } -.walk.is-live .walker-layer { position: absolute; inset: 0; display: block; overflow: hidden; } -.walk.is-live .walker-layer img { display: block; width: auto; height: 100%; max-width: none; will-change: transform; } -.walk.is-live .walker-still { display: none; } -.walk.is-live .walker-floor { position: absolute; left: 12%; bottom: -2.2%; z-index: -1; display: block; width: 72%; height: 5.5%; border-radius: 50%; background: radial-gradient(closest-side, rgba(45, 36, 26, 0.26), rgba(45, 36, 26, 0.1) 60%, rgba(45, 36, 26, 0)); transform-origin: 50% 50%; } -.walk.is-live .walk-rail { position: absolute; top: 0; left: 0; z-index: 2; display: flex; gap: 6px; margin: 0; padding: 0; list-style: none; } -.walk.is-live .walk-rail li { position: relative; flex: 1; min-width: 0; padding-top: 12px; } -.walk.is-live .walk-rail li::before, .walk.is-live .rail-fill { position: absolute; top: 0; left: 0; width: 100%; height: 2px; border-radius: 2px; content: ""; } -.walk.is-live .walk-rail li::before { background: var(--line); } -.walk.is-live .rail-fill { background: var(--ink); transform: scaleX(0); transform-origin: 0 50%; } -.walk.is-live .rail-name { display: block; overflow: hidden; color: var(--muted); font-size: 12px; font-weight: 500; white-space: nowrap; text-overflow: ellipsis; transition: color 0.3s var(--ease); } -.walk.is-live .walk-rail li.is-here .rail-name { color: var(--ink); } - -.walk.is-live .walk-end { position: absolute; top: 0; left: 0; z-index: 2; width: 420px; padding: 0; opacity: 0; pointer-events: none; will-change: transform, opacity; } -.walk.is-live .walk-end.is-on { pointer-events: auto; } -.walk.is-live .walk-end-t { font-size: clamp(1.9rem, 3.4vw, 2.9rem); line-height: 1; letter-spacing: -0.035em; } -.walk.is-live .walk-end-p { margin: var(--space-3) 0 var(--space-4); font-size: 17px; } - -/* Fallbacks before the script measures the hero (it then sets --mx and --mh in px). */ -.walk.is-live .walk-stage { --mh: min(580px, calc(100svh - 300px)); --mw: calc(var(--mh) * 0.535); --mx: calc(87% - var(--mw) * 0.5); --mb: 24px; } +.poster-name { font-family: var(--font-display); font-size: clamp(1.6rem, 2.8vw, 2.7rem); font-weight: 700; line-height: 1.04; letter-spacing: -0.03em; text-wrap: balance; } +.poster-acts { grid-area: acts; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin: 0; } +.act { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 44px; padding: 0 18px; border: 1px solid rgba(27, 24, 56, 0.35); border-radius: 999px; color: #1b1838; font-size: 15px; font-weight: 600; text-decoration: none; white-space: nowrap; transition: background-color 0.2s var(--ease), filter 0.2s var(--ease); } +.act:hover { background: rgba(27, 24, 56, 0.06); } +.act-main { border-color: var(--gold); background: var(--gold); } +.act-main:hover { background: var(--gold); filter: brightness(1.06); } +.act-req { flex-basis: 100%; color: var(--muted); font-size: 13px; } +.poster-desc { grid-area: desc; max-width: 52ch; margin: 0; color: var(--ink-soft); font-size: 16px; line-height: 1.45; text-wrap: pretty; } +.poster-shot { grid-area: shot; display: block; width: 100%; height: auto; border-radius: 10px; background: var(--night); box-shadow: 0 10px 26px rgba(27, 24, 56, 0.35); object-fit: cover; } + +/* The card's foot: night glass with cream type, a gold dot for each point. */ +.note { padding: 18px 24px 20px; border-top: 1px solid rgba(233, 196, 106, 0.28); background: var(--night-2); color: var(--ink); font-size: 15px; line-height: 1.45; } +.note-what { margin: 0; font-weight: 600; text-wrap: pretty; } +.note-for { margin: 8px 0 0; color: var(--muted); font-size: 14px; text-wrap: pretty; } +.note-k { margin-right: 6px; color: var(--gold); font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; } +.note-pts { display: grid; gap: 6px; margin: 12px 0 0; padding: 0; list-style: none; color: var(--ink-soft); font-size: 14px; text-wrap: pretty; } +.note-pts li { position: relative; padding-left: 16px; } +.note-pts li::before { position: absolute; top: 0.62em; left: 0; width: 6px; height: 6px; border-radius: 50%; background: var(--gold); content: ""; } +.note-go { display: inline-flex; align-items: center; min-height: 44px; margin-top: 4px; color: var(--gold); font-weight: 600; text-decoration: none; } +.note-go:hover { text-decoration: underline; } @media (max-width: 700px) { .walk-head h2 { font-size: clamp(2rem, 9vw, 2.6rem); } .walk-note { font-size: 16px; } - .walk-stage { width: calc(100% - 40px); margin-top: var(--space-6); grid-template-columns: 1fr; } - .walk-track { grid-column: 1; grid-row: 2; } - .walker { position: static; grid-row: 1; width: 42%; max-width: 170px; } - .walk-end { grid-column: 1; } - .poster { padding: 18px 18px 16px; } - .walk.is-live .walk-stage { --mh: 320px; --mx: calc(96% - var(--mw)); --mb: 20px; } - .walk.is-live .poster { display: flex; align-items: stretch; gap: 6px; padding: 14px 16px 12px; } - .walk.is-live .poster::before { top: -6px; width: 48px; height: 12px; margin-left: -24px; } - .walk.is-live .poster-head, .walk.is-live .poster-body { gap: 2px; } - .walk.is-live .poster-tag { font-size: 12px; } - .walk.is-live .poster-name { font-size: 1.3rem; } - .walk.is-live .poster-desc { font-size: 15px; line-height: 1.4; } - .walk.is-live .poster-go { font-size: 14px; } - .walk.is-live.is-tight .poster { padding: 10px 14px 9px; } - .walk.is-live.is-tight .poster-name { font-size: 1.15rem; } - .walk.is-live.is-tight .poster-desc { font-size: 14px; line-height: 1.35; } - .walk.is-live.is-tight .poster-go { font-size: 13px; } - .walk.is-live .rail-name { display: none; } - .walk.is-live .walk-rail li { padding-top: 0; height: 10px; } - .walk.is-live .walk-end-p { font-size: 15px; } - /* Tall phones: the stop list fills the floor below her. */ - .walk.is-live.is-list .walk-rail { flex-direction: column; gap: 10px; } - .walk.is-live.is-list .walk-rail li { flex: none; height: auto; padding: 0 0 9px; } - .walk.is-live.is-list .walk-rail li::before, .walk.is-live.is-list .rail-fill { top: auto; bottom: 0; } - .walk.is-live.is-list .rail-name { display: block; font-size: 17px; font-weight: 600; letter-spacing: -0.01em; } + .walk-stage { width: calc(100% - 40px); margin-top: var(--space-6); } + .walk-end { width: calc(100% - 40px); } + .poster { gap: 12px; padding: 18px 16px 16px; } + .poster-icon { width: 56px; height: 56px; border-radius: 13px; } +} + +/* Live. The stage pins for the length of the scroller. Each app gets a dwell (its card rests + in front) and an eased change to the next: the card falls back and fades while the next + one rises from depth. The script writes each frame's transforms and opacities, Mimi's + walk-cycle frame and the bubble's fade. */ +@media (prefers-reduced-motion: no-preference) and (min-height: 560px) { + .js .walk { --wall-x: 20px; --mimi-h: 150px; } + .js .walk-scroller { height: calc(100vh + 7 * 64vh); height: calc(100svh + 7 * 64svh); } + .js .walk-stage { position: sticky; top: 0; display: block; width: 100%; height: 100vh; height: 100svh; margin: 0; overflow: clip; } + .js .walk-track { position: absolute; display: block; perspective: 1100px; perspective-origin: 50% 45%; } + .js .walk .stop { position: absolute; top: 0; left: 0; width: 100%; opacity: 0; pointer-events: none; will-change: transform, opacity; box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6); } + .js .walk .stop.is-on { pointer-events: auto; } + + .walk-mimi { position: absolute; left: calc(var(--wall-x) - 4px); bottom: 52px; height: var(--mimi-h); aspect-ratio: 162 / 420; background: url("/assets/mimi-walk-front.webp") 0 0 / 1200% 100% no-repeat; } + .walk-mimi::after { position: absolute; left: 12%; right: 12%; bottom: -6px; height: 12px; border-radius: 50%; background: radial-gradient(closest-side, rgba(0, 0, 0, 0.55), transparent); content: ""; } + .walk-say { position: absolute; margin: 0; padding: 12px 16px; border-radius: 16px; background: #fdfcf8; color: #1b1838; font-size: 15px; font-weight: 600; line-height: 1.4; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); text-wrap: pretty; word-break: keep-all; } + .walk-say::before { display: block; margin-bottom: 4px; color: #7a5a12; font-size: 11px; font-weight: 700; letter-spacing: 0.12em; content: "MIMI"; } + .walk-say::after { position: absolute; width: 16px; height: 16px; background: #fdfcf8; content: ""; transform: rotate(45deg); } + + .walk-prog { position: absolute; right: var(--wall-x); bottom: 18px; left: var(--wall-x); display: flex; align-items: center; gap: 16px; color: var(--muted); font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; } + .walk-prog-at { color: var(--gold); } + .walk-prog-bar { position: relative; flex: 1; height: 2px; background: rgba(233, 196, 106, 0.22); } + .walk-prog-fill { position: absolute; inset: 0; background: var(--gold); transform: scaleX(0); transform-origin: 0 50%; will-change: transform; } + .walk-prog-dot { position: absolute; top: 50%; width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%; background: var(--night); box-shadow: 0 0 0 2px rgba(233, 196, 106, 0.45); } + .walk-prog-dot.is-lit { background: var(--gold); } +} + +/* Phones and narrow tablets: the card across the top, Mimi small at the bottom left with + her bubble beside her. The card keeps its points; the longer copy is in the static view. */ +@media (prefers-reduced-motion: no-preference) and (min-height: 560px) and (max-width: 900px) { + .js .walk-track { top: 76px; right: var(--wall-x); left: var(--wall-x); } + .js .walk .poster { grid-template-areas: "icon head" "desc desc" "shot shot" "acts acts"; } + .js .walk .note-what, .js .walk .note-for { display: none; } + .js .walk .note-pts { margin-top: 0; } + .js .walk .note { padding: 12px 16px 10px; } + .walk-say { right: var(--wall-x); bottom: calc(52px + var(--mimi-h) * 0.42); left: calc(var(--wall-x) + var(--mimi-h) * 0.386 + 12px); } + .walk-say::after { left: -6px; bottom: 18px; } +} +@media (prefers-reduced-motion: no-preference) and (min-height: 560px) and (max-height: 760px) and (max-width: 900px) { + .js .walk { --mimi-h: 110px; } + .walk-say { bottom: calc(52px + var(--mimi-h) * 0.12); } + .js .walk .note { display: none; } + .js .walk .poster-shot { max-height: 150px; } +} + +@media (prefers-reduced-motion: no-preference) and (min-height: 560px) and (max-width: 700px) { + .js .walk .poster-shot { max-height: 180px; } +} +@media (prefers-reduced-motion: no-preference) and (min-height: 900px) and (min-width: 701px) and (max-width: 900px) { + .js .walk { --wall-x: 32px; --mimi-h: 190px; } + .js .walk-track { top: 100px; } + .js .walk .poster-shot { max-height: 380px; } +} + +/* Wide screens: Mimi full height on the left with her bubble above her; one tall card to her + right, icon and name with the links beside them, the screen below, the details in two + columns underneath. */ +@media (prefers-reduced-motion: no-preference) and (min-height: 560px) and (min-width: 901px) { + .js .walk { + --wall-x: max(48px, calc(50vw - 620px)); + --mimi-h: min(540px, calc(100svh - 300px)); + --card-w: min(780px, calc(100vw - 2 * var(--wall-x) - 360px), max(560px, calc((100svh - 540px) * 1.74 + 56px))); + } + .js .walk-track { top: max(96px, calc(50svh - 380px)); left: calc(var(--wall-x) + 360px); width: var(--card-w); } + .js .walk .poster { grid-template-columns: 84px minmax(0, 1fr) auto; grid-template-areas: "icon head acts" "icon desc acts" "shot shot shot"; row-gap: 6px; padding: 26px 28px 22px; } + .js .walk .poster-icon { align-self: start; } + .js .walk .poster-head { align-self: end; } + .js .walk .poster-desc { align-self: start; font-size: 15px; } + .js .walk .poster-shot { margin-top: 12px; } + .js .walk .poster:not(:has(.poster-icon)) { grid-template-columns: 0 minmax(0, 1fr) auto; } + .js .walk .poster-acts { flex-direction: column; align-items: stretch; } + .js .walk .act-req { text-align: center; } + .js .walk .poster-shot { max-height: calc(100svh - 480px); } + .js .walk .note { display: grid; grid-template-columns: 1.05fr 1fr; column-gap: 24px; padding: 18px 28px 20px; } + .js .walk .note-what, .js .walk .note-for { grid-column: 1; } + .js .walk .note-pts { grid-column: 2; grid-row: 1 / span 2; align-content: start; margin: 0; } + .js .walk .note-go { grid-column: 2; justify-self: start; margin-top: 2px; min-height: 32px; } + .walk-mimi { left: calc(var(--wall-x) + 20px); bottom: 56px; } + .walk-say { left: calc(var(--wall-x) - 36px); bottom: calc(56px + var(--mimi-h) + 18px); width: 330px; padding: 14px 18px; font-size: 17px; } + .walk-say::after { left: 44px; bottom: -7px; } + .walk-prog { right: var(--wall-x); left: var(--wall-x); bottom: 28px; } +} +/* Short laptop screens: the card keeps a usable width, so its foot shrinks to the points in + one row; the full copy stays in the static view and on each app's own page. */ +@media (prefers-reduced-motion: no-preference) and (min-height: 560px) and (max-height: 820px) and (min-width: 901px) { + .js .walk-track { top: 88px; } + .js .walk .note { display: flex; flex-wrap: wrap; align-items: center; gap: 2px 22px; padding: 10px 28px 10px; } + .js .walk .note-what, .js .walk .note-for { display: none; } + .js .walk .note-pts { display: contents; } + .js .walk .note-go { min-height: 32px; margin: 0; } + .js .walk .poster-shot { max-height: calc(100svh - 420px); } } /* r7: night sky. The page is the X header's painted night: deep navy and violet, cream @@ -1900,7 +1890,6 @@ a:focus-visible, button:focus-visible, [tabindex]:focus-visible { outline: 2px s .hero::after { position: absolute; inset: 0; z-index: -1; background: linear-gradient(90deg, rgba(10, 14, 40, 0.84) 0%, rgba(10, 14, 40, 0.62) 34%, rgba(10, 14, 40, 0) 60%), linear-gradient(0deg, var(--night) 0%, rgba(13, 19, 51, 0) 16%); content: ""; pointer-events: none; } .hero-stage { cursor: auto; } .hero-stage::before, .hero-stage::after { display: none; } -.mimi { visibility: hidden; } .wordmark { display: flex; flex-wrap: nowrap; gap: 0 0.5em; margin-top: clamp(24px, 6vh, 72px); padding-top: 0; font-family: "DM Sans", sans-serif; font-weight: 700; letter-spacing: 0.14em; line-height: 1; color: var(--ink); text-shadow: 0 2px 24px rgba(8, 10, 32, 0.45); white-space: nowrap; } .wm-207, .wm-studio, html:lang(ko) .wm-207, html:lang(ko) .wm-studio { display: inline; margin: 0; padding: 0; font-family: "DM Sans", sans-serif; font-size: clamp(56px, 7.2vw, 112px); letter-spacing: inherit; } .hero-sub { margin-top: clamp(14px, 1.6vw, 22px); padding-left: 0.2em; color: #efd9a0; font-family: "DM Sans", sans-serif; font-size: clamp(13px, 1.25vw, 18px); font-weight: 500; letter-spacing: 0.34em; text-transform: uppercase; } @@ -1936,22 +1925,8 @@ html:lang(ko) .hero-sub { letter-spacing: 0.24em; } .card-badge { color: #121a44; } .footer-word { opacity: 0.1; } -/* Walk: a violet wall with faint gold seams; cream posters keep their dark ink. */ +/* Walk: cream posters keep their dark ink. */ .poster { --ink-soft: #3b3654; --muted: #5b5672; --accent: #7a5a12; color: #1b1838; } -.poster:focus-visible { outline-color: var(--gold); } -.walk-paint { display: none; } -.walk.is-live .walk-paint { position: absolute; inset: 0; z-index: -1; display: block; pointer-events: none; } -.walk.is-live .walk-paint img { width: 100%; height: 100%; object-fit: cover; object-position: 30% 40%; } -.walk.is-live .walk-stage { background: var(--night); } -.walk.is-live .walk-stage::before { background: radial-gradient(closest-side, rgba(233, 196, 106, 0.16) 0%, rgba(233, 196, 106, 0.06) 55%, rgba(233, 196, 106, 0) 100%); } -.walk.is-live .wall-face { background: linear-gradient(180deg, rgba(20, 26, 69, 0.08), rgba(20, 26, 69, 0.34) 70%, rgba(13, 19, 51, 0.6)); } -.walk.is-live .wall-ledge { background: rgba(233, 196, 106, 0.4); } -.walk.is-live .wall-seams { background: linear-gradient(90deg, rgba(233, 196, 106, 0.2) 0 1px, transparent 1px) 0 0 / 200px 100% repeat-x; } -.walk.is-live .poster { background: radial-gradient(120% 90% at 20% 10%, rgba(255, 252, 240, 0.9), rgba(255, 252, 240, 0) 60%), linear-gradient(175deg, #f6eed8, #ece2c6); box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset, 0 18px 40px rgba(4, 6, 20, 0.55); } -.walk.is-live .poster::after { background: linear-gradient(175deg, #fbf5e2, #f3e9cf); } -.walk.is-live .poster::before { background: rgba(233, 196, 106, 0.5); } -.walk.is-live .walker-floor { background: radial-gradient(closest-side, rgba(4, 6, 20, 0.6), rgba(4, 6, 20, 0.25) 60%, rgba(4, 6, 20, 0)); } -.walk.is-live .rail-fill { background: var(--gold); } @media (max-width: 700px) { .dock .brand-word { display: inline; font-size: 12px; letter-spacing: 0.13em; } diff --git a/assets/home.js b/assets/home.js index 4d9f260..2d6b88a 100644 --- a/assets/home.js +++ b/assets/home.js @@ -112,17 +112,6 @@ window.addEventListener("pointerup", onUp); window.addEventListener("pointercancel", onUp); - var mimi = document.querySelector("[data-mimi]"); - if (mimi && window.matchMedia("(pointer: fine)").matches) { - window.addEventListener("pointermove", function (event) { - if (reduceMedia.matches) return; - var nx = event.clientX / window.innerWidth - 0.5; - var ny = event.clientY / window.innerHeight - 0.5; - mimi.style.setProperty("--mimi-x", (nx * 14).toFixed(1) + "px"); - mimi.style.setProperty("--mimi-y", (ny * 8).toFixed(1) + "px"); - }, { passive: true }); - } - if (document.readyState === "complete") { scheduleLoad(); } else { @@ -130,347 +119,106 @@ } })(); -/* Walk with Mimi. Scroll drives one timeline, measured in "stops" (t from 0 to TOTAL). - Stop i owns [b, b + 1) with b = INTRO + i: - ..b+0.40 she walks; poster i comes along the wall from the far end - b+0.40..b+0.92 she stands beside it; the poster is lit - b+0.92.. she walks on; the poster slides on toward her and fades before it reaches her - Mimi takes over from the hero Mimi: same figure height, same body centre, same feet, and - she only shows once her whole figure is inside the frame. She never moves after that and - is never mirrored. - The wall is one CSS 3D plane (rotateY) seen through the stage's perspective, so the wall, - its seams and the posters pasted on it share one vanishing point. On wide screens the - wall runs to her left and the featured poster hangs there at her height; on phones it - runs above her head and the stop list sits on the floor below her. - Walk frames advance 24 per stop of wall travel everywhere (12 for the half-stop intro), - so her steps keep pace with the wall, and every arrival lands on the standing frame. - Smoothing: the drawn time eases toward the scroll time, like a short scrub. */ +/* Walk. One scroll handler drives the pinned stage (see the CSS): per app a dwell where its + card rests in front, then an eased change where it falls back in depth and the next card + rises to the front. Mimi walks in place during a change and her bubble fades out and back + with the next app's line. Reduced motion or a short screen keeps the static grid. */ (function () { - "use strict"; var section = document.querySelector("[data-walk]"); - if (!section || !window.requestAnimationFrame) return; - if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; - + if (!section) return; + var live = window.matchMedia("(prefers-reduced-motion: no-preference) and (min-height: 560px)"); var scroller = section.querySelector("[data-walk-scroller]"); var stage = section.querySelector("[data-walk-stage]"); - var walker = section.querySelector("[data-walker]"); - var walkImg = section.querySelector("[data-walk-strip]"); - var floor = section.querySelector("[data-walk-floor]"); - var end = section.querySelector("[data-walk-end]"); - var wall = section.querySelector("[data-wall]"); - var face = section.querySelector("[data-wall-face]"); - var ledge = section.querySelector("[data-wall-ledge]"); - var seams = section.querySelector("[data-seam]"); - var track = section.querySelector("[data-walk-track]"); var stops = [].slice.call(section.querySelectorAll("[data-stop]")); - var fills = [].slice.call(section.querySelectorAll("[data-rail-fill]")); - var railItems = fills.map(function (f) { return f.parentNode; }); - var rail = railItems.length ? railItems[0].parentNode : null; - var heroImg = document.querySelector("[data-mimi]"); - var n = stops.length; - - var INTRO = 0.25, ARRIVE = 0.40, LEFT = 0.92; - var LIT_A = 0.44, LIT_B = 0.88; - var WALK_GAP = (1 - LEFT) + ARRIVE; // 0.48 - var END_AT = INTRO + n, STAND = END_AT + ARRIVE, TOTAL = STAND + 0.35; - var SEG_SVH = 96; - // Sprite geometry (12 cells of 162 x 420; hero image 431 x 990), from the alpha channel. - var CELL_W = 162, CELL_H = 420, FRAMES = 12; - var HAND_FRAME = 10, HAND_BOX = [7, 3, 159, 405]; // both feet planted: hand-off and stops - var HERO_BOX = [8, 6, 420, 982], MIN_X = 2, MIN_Y = -2; // widest / tallest walk frame (feet pinned) - var FOOT = [417, 415, 412, 408, 405, 409, 411, 414, 411, 408, 405, 409]; // lowest row per cell - var INTRO_SPAN = 0.5; // the intro walk covers half a stop - var K = 0.86, TILT = 20 * Math.PI / 180; // far edge of the featured poster vs near edge - var GAP = 10; - - var vw = 0, vh = 0, mh = 1, mobile = false, range = 1, top0 = 0, mb = 0, footGap = 0, fadeIn = 1; - var L = 0, oy = 0, P = 1000, cs = 1, sn = 0, pwp = 0, step = 0, seamGap = 1, limit = 0, railTop = 0; - var heights = []; - var cur = -1, target = 0, last = 0, running = false, ready = false; - var memo = [], drawn = {}; - - section.classList.add("is-live"); - scroller.style.height = (TOTAL * SEG_SVH + 100) + "vh"; - scroller.style.height = (TOTAL * SEG_SVH + 100) + "svh"; - - function clamp(x) { return x < 0 ? 0 : x > 1 ? 1 : x; } - function easeOut(x) { return 1 - Math.pow(1 - x, 3); } - function easeInOut(x) { return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; } - function px(v) { return Math.round(v) + "px"; } - function opac(v) { return v < 0.002 ? "0" : v > 0.998 ? "1" : v.toFixed(3); } - function put(el, key, prop, val) { - var m = memo[key] || (memo[key] = {}); - if (m[prop] !== val) { m[prop] = val; el.style[prop] = val; } - } - function box(el, x, y, w, h) { - el.style.left = px(x); el.style.top = px(y); el.style.width = px(w); el.style.height = px(h); - } - - // The hero Mimi image in page pixels, ignoring her pointer-follow nudge (a translate). - function heroBox() { - if (!heroImg || !heroImg.offsetHeight) return null; - var x = 0, y = 0; - for (var el = heroImg; el; el = el.offsetParent) { x += el.offsetLeft; y += el.offsetTop; } - return { x: x, y: y, w: heroImg.offsetWidth, h: heroImg.offsetHeight }; - } - - // Plane x (0 = near edge of the featured poster) to screen x; perspective-origin x is L. - function scale(xp) { return P / (P + xp * sn); } - function proj(xp) { return L + xp * cs * scale(xp); } - function unproj(sx) { return P * (L - sx) / ((sx - L) * sn - cs * P); } - - function layout() { - var i, ph = 0; - vw = stage.clientWidth; vh = stage.clientHeight; - mobile = vw <= 700; - var dock = mobile ? 76 : 92; - var side = mobile ? 12 : Math.round(Math.max(24, vw * 0.033)); - ["--mh", "--mw", "--mx", "--mb"].forEach(function (p) { stage.style.removeProperty(p); }); - - // Mimi: the hero's figure, feet and body centre, carried over exactly. - var hb = heroBox(), H, ml, wt; - if (hb) { - var figH = hb.h * (HERO_BOX[3] + 1 - HERO_BOX[1]) / 990; - H = figH * CELL_H / (HAND_BOX[3] + 1 - HAND_BOX[1]); - var foot = hb.y + hb.h * (HERO_BOX[3] + 1) / 990; - wt = foot - H * (HAND_BOX[3] + 1) / CELL_H; - var heroCx = hb.x + hb.w * (HERO_BOX[0] + HERO_BOX[2] + 1) / 2 / 431; - var boxCx = hb.x + hb.w / 2; - var mw0 = H * CELL_W / CELL_H; - var bodyOff = ((HAND_BOX[0] + HAND_BOX[2] + 1) / 2 - CELL_W / 2) * mw0 / CELL_W; - // Split the difference between the box centre and the body centre (both stay within 2px). - ml = boxCx - mw0 / 2 + ((heroCx - boxCx) - bodyOff) / 2; - } else { - H = walker.offsetHeight || 320; wt = vh - 24 - H; ml = walker.offsetLeft; - } - var mw = H * CELL_W / CELL_H; - mh = H; - // The cell runs a few px below her feet; that transparent strip may hang below the stage. - mb = vh - wt - H; - stage.style.setProperty("--mh", H.toFixed(2) + "px"); - stage.style.setProperty("--mw", mw.toFixed(2) + "px"); - stage.style.setProperty("--mx", ml.toFixed(2) + "px"); - stage.style.setProperty("--mb", mb.toFixed(2) + "px"); - section.classList.toggle("is-above", mobile); - // She fades in over the scroll between "whole figure in frame" and the pin, never later. - footGap = Math.max(0, vh - wt - H * (HAND_BOX[3] + 1) / CELL_H); - fadeIn = Math.max(1, Math.min(Math.max(60, vh * 0.12), footGap)); - - var figL = ml + MIN_X * mw / CELL_W, headY = wt + MIN_Y * H / CELL_H, feetY = wt + H; - // Stop list: a thin rail at her feet on wide screens; on tall phones a list on the floor. - var listRoom = vh - feetY - 24; - var asList = mobile && listRoom >= 200; - section.classList.toggle("is-list", asList); - railTop = mobile ? (asList ? feetY + 30 : feetY - 6) : feetY - 26; - - // The featured poster at its stop, on screen: [xl, xr]. Wide screens: on the wall to her - // left, centred between the dock and the rail. Phones: across the top, above her head. - var xl, xr; - if (!mobile) { - xr = figL - Math.max(24, H * 0.06); - xl = Math.max(side, xr - 640); - limit = figL - 2; - } else { - xl = side; xr = vw - side; limit = Infinity; - } - // Perspective: heights fall off linearly toward the vanishing point vx, so the far edge - // of the featured poster is K times its near edge. TILT sets how much it is foreshortened. - var vx = (xr - K * xl) / (1 - K); - cs = Math.cos(TILT); sn = Math.sin(TILT); - L = xl; P = (vx - xl) * Math.tan(TILT); - pwp = (xr - xl) / (K * cs); - step = pwp * 1.12; - seamGap = step / 2; - - section.classList.remove("is-tight"); - // Nearer the left edge the wall is closer, so a poster there is taller (sNear times). - var sNear = Math.max(1, scale(unproj(side))); - for (i = 0; i < n; i++) stops[i].style.width = px(pwp); - for (var pass = 0; pass < 2; pass++) { - ph = 0; heights = []; - for (i = 0; i < n; i++) { heights[i] = stops[i].offsetHeight; ph = Math.max(ph, heights[i]); } - if (!mobile) oy = Math.max(dock + 12 + ph / 2, Math.min((dock + railTop) / 2, railTop - 14 - ph / 2 * sNear)); - else oy = headY - GAP; - if (!mobile || oy - ph >= dock + 6 || pass) break; - section.classList.add("is-tight"); // short band above her head - } - var half = mobile ? 1 : 0.5; - stage.style.perspective = Math.round(P) + "px"; - stage.style.perspectiveOrigin = px(L) + " " + px(oy); - var plane = "rotateY(" + TILT.toFixed(5) + "rad)"; - [wall, track].forEach(function (el) { el.style.left = px(L); el.style.transform = plane; }); - for (i = 0; i < n; i++) stops[i].style.top = px(oy - heights[i] * half); - - // Wall: from past the near (left) edge to past the right edge. Its top clears the - // posters; its foot meets the floor just behind her feet. - var x0 = unproj(-40), x1 = Math.max(unproj(vw + 40), pwp * 2); - var sM = scale(unproj(ml + mw / 2)); - var yB = oy + (wt + H * 0.95 - oy) / sM; - var yT = oy - Math.max(ph * half + 48, (oy - Math.max(0, wt - H * 0.1)) / scale(unproj(vw))); - box(face, x0, yT, x1 - x0, yB - yT); - box(ledge, x0, yB - 1, x1 - x0, 3); - box(seams, x0 - seamGap, yT, x1 - x0 + seamGap * 2, yB - yT); - seams.style.backgroundSize = seamGap.toFixed(2) + "px 100%"; - - if (!mobile) { - end.style.width = px(Math.max(240, Math.min(460, xr - side))); end.style.left = px(side); - end.style.top = px(Math.max(dock + 10, oy - end.offsetHeight / 2)); - } else { - end.style.width = px(vw - side * 2); end.style.left = px(side); - end.style.top = px(Math.max(dock + 6, oy - end.offsetHeight)); - } - if (rail) { - rail.style.left = side + "px"; - rail.style.width = Math.round(asList ? vw - side * 2 : mobile ? ml - side - 12 : Math.min(520, ml - side - 40)) + "px"; - rail.style.top = Math.round(railTop) + "px"; - } - - range = Math.max(1, scroller.offsetHeight - vh); - top0 = scroller.getBoundingClientRect().top + window.pageYOffset; - memo = []; drawn = {}; - } - - function scrollTime() { - var p = clamp((window.pageYOffset - top0) / range); - return p * TOTAL; - } - - // Where the walk is, in stops: -1 at the start, i while paused at stop i, n at the end. - // Each walk eases in and out. - function along(t) { - var w0 = INTRO + ARRIVE; - if (t <= 0) return -1; - if (t < w0) return -1 + easeInOut(t / w0); - for (var i = 0; i < n; i++) { - var b = INTRO + i; - if (t < b + LEFT) return i; - var e = b + 1 + ARRIVE; - if (t < e) return i + easeInOut((t - b - LEFT) / (e - b - LEFT)); + var N = stops.length, DWELL = 0.55, BACK = -420, DEPTH = -600, FRAMES = 12; + var top = 0, run = 1, active = -1, ticking = false, on = false; + + var mimi = document.createElement("div"); + mimi.className = "walk-mimi"; + mimi.setAttribute("aria-hidden", "true"); + var say = document.createElement("p"); + say.className = "walk-say"; + say.setAttribute("aria-hidden", "true"); + + var prog = document.createElement("div"); + prog.className = "walk-prog"; + prog.setAttribute("aria-hidden", "true"); + var dots = ""; + for (var d = 0; d < N; d++) dots += ''; + prog.innerHTML = '01' + dots + "0" + N + ""; + var at = prog.firstChild, fill = prog.querySelector(".walk-prog-fill"), dotEls = prog.querySelectorAll(".walk-prog-dot"); + + function ease(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(2 - 2 * t, 3) / 2; } + + function measure() { + top = scroller.getBoundingClientRect().top + window.pageYOffset; + run = Math.max(1, scroller.offsetHeight - stage.offsetHeight); + } + + function light(i) { + for (var k = 0; k < N; k++) { + stops[k].classList.toggle("is-on", k === i); + dotEls[k].classList.toggle("is-lit", k <= i); } - return n; - } - - // Walk frame for time t: the standing frame at the stops and at the end, otherwise the - // cycle, 24 frames per stop of wall travel. - function frame(t) { - if (t >= STAND || t <= 0) return HAND_FRAME; - var a = along(t); - if (a === Math.round(a)) return HAND_FRAME; - var f = (HAND_FRAME + Math.floor((a < 0 ? a * INTRO_SPAN : a) * 24 + 1e-6)) % FRAMES; - return f < 0 ? f + FRAMES : f; - } - - function drawMimi(t) { - var f = ready ? frame(t) : HAND_FRAME; - // Keep the lowest foot on the floor: each cell's figure ends at a slightly different row. - var dy = (HAND_BOX[3] - FOOT[f]) * mh / CELL_H; - put(walkImg, "wi", "transform", "translate3d(" + (-f * 100 / FRAMES).toFixed(4) + "%," + dy.toFixed(1) + "px,0)"); - // Contact shadow breathes with the stride: tighter on a passing step, wider at full stride. - var stride = f === HAND_FRAME ? 0.5 : Math.abs(Math.cos((f - HAND_FRAME) / 6 * Math.PI)); - put(floor, "fl", "transform", "scaleX(" + (0.92 + stride * 0.1).toFixed(3) + ")"); - } - - // She appears only once her whole figure is in the frame (the stage is still sliding up - // into view until it pins), and fades in from there. - function drawEntry() { - var a = window.pageYOffset - (top0 - footGap); - put(walker, "wo", "opacity", opac(clamp(a / fadeIn))); - } - - function drawWall(a) { - var s = ((a * step) % seamGap + seamGap) % seamGap; - put(seams, "sm", "transform", "translate3d(" + s.toFixed(1) + "px,0,0)"); - } - - function drawStops(t, a) { - var lastOp = 1; - for (var i = 0; i < n; i++) { - var u = t - (INTRO + i), el = stops[i], key = "s" + i; - var xp = (a - i) * step, op = 1, nearX = unproj(-40), farX = unproj(vw + 40); - var right = xp + pwp > nearX ? proj(xp + pwp) : -1e4; - if (right < -4 || xp > farX || proj(xp) > vw + 4) op = 0; - // Wide screens: once it starts to slide away toward her it fades, and it is gone - // before it could reach her. - else if (!mobile && xp > 0) op = clamp((limit - right) / Math.max(1, limit - proj(pwp))); - if (op === 0) xp = Math.min(Math.max(xp, nearX - pwp * 1.5), farX); - put(el, key + "t", "transform", "translate3d(" + xp.toFixed(1) + "px,0,0)"); - put(el, key + "o", "opacity", opac(op)); - if (i === n - 1 && a > n - 1) lastOp = op; - var on = op > 0.5, lit = u >= LIT_A && u < LIT_B; - if (drawn[key + "on"] !== on) { drawn[key + "on"] = on; el.classList.toggle("is-on", on); } - if (drawn[key + "lit"] !== lit) { - drawn[key + "lit"] = lit; - el.classList.toggle("is-here", lit); - if (railItems[i]) railItems[i].classList.toggle("is-here", lit); - } - if (fills[i]) put(fills[i], key + "f", "transform", "scaleX(" + clamp((u - (LEFT - 1)) / WALK_GAP).toFixed(3) + ")"); + at.textContent = "0" + (i + 1); + say.textContent = stops[i].getAttribute("data-say"); + active = i; + } + + // Depth and opacity for card k while card i hands over to i + 1 (move runs 0 to 1). + // The old card is mostly gone by the midpoint and the new one paints over it, so the two + // never read as one muddle of half-opaque text. + function place(k, i, move) { + var z = 0, o = 0, out = Math.min(1, move * 1.6); + if (k === i) { z = BACK * out; o = 1 - out; } + else if (k === i + 1) { z = DEPTH * (1 - move); o = Math.min(1, Math.max(0, (move - 0.15) / 0.35)); } + stops[k].style.transform = "translate3d(0,0," + z.toFixed(1) + "px)"; + stops[k].style.opacity = o.toFixed(3); + } + + function tick() { + ticking = false; + var t = Math.min(Math.max((window.pageYOffset - top) / run, 0), 1) * N; + var i = Math.min(N - 1, Math.floor(t)); + var move = i < N - 1 ? ease(Math.min(Math.max((t - i - DWELL) / (1 - DWELL), 0), 1)) : 0; + var pos = i + move, here = Math.round(pos); + for (var k = 0; k < N; k++) place(k, i, move); + var frame = move > 0 && move < 1 ? Math.floor(move * 2 * FRAMES) % FRAMES : 0; + mimi.style.backgroundPositionX = (frame / (FRAMES - 1) * 100).toFixed(2) + "%"; + say.style.opacity = Math.min(1, Math.abs(move - 0.5) * 2).toFixed(3); + fill.style.transform = "scaleX(" + (pos / (N - 1)).toFixed(4) + ")"; + if (here !== active) light(here); + } + + function onScroll() { if (!ticking) { ticking = true; requestAnimationFrame(tick); } } + + function mode() { + on = live.matches; + window.removeEventListener("scroll", onScroll); + if (!on) { + [mimi, say, prog].forEach(function (el) { el.remove(); }); + stops.forEach(function (s) { s.style.transform = ""; s.style.opacity = ""; }); + return; } - var v = t - END_AT; - // The closing block takes over as the last poster fades, so the wall is never bare. - var k = Math.max(easeOut(clamp((v - 0.1) / 0.3)), clamp(1 - lastOp * 2)); - put(end, "et", "transform", "translate3d(0," + ((1 - k) * (mobile ? -24 : 24)).toFixed(1) + "px,0)"); - put(end, "eo", "opacity", opac(k)); - if (drawn.end !== k > 0.5) { drawn.end = k > 0.5; end.classList.toggle("is-on", drawn.end); } - } - - // The intro covers half a stop, so the first poster is already half on the wall when she - // sets off (the wall is never bare); her steps are scaled to match in frame(). - function draw(t) { var a = along(t); if (a < 0) a *= INTRO_SPAN; drawWall(a); drawStops(t, a); drawMimi(t); } - - function tick(now) { - var dt = Math.min(0.05, (now - last) / 1000 || 0.016); - last = now; - var d = target - cur; - if (Math.abs(d) > 1.5) cur = target - (d > 0 ? 0.6 : -0.6); // long jumps: skip ahead, then ease - else cur += d * (1 - Math.exp(-dt / 0.09)); - if (Math.abs(target - cur) < 0.0005) cur = target; - draw(cur); - if (cur !== target) requestAnimationFrame(tick); else running = false; - } - - function kick() { - target = scrollTime(); - drawEntry(); - if (cur < 0) { cur = target; draw(cur); return; } - if (!running) { running = true; last = performance.now(); requestAnimationFrame(tick); } - } - - var resizeTimer = 0; - function onResize() { - clearTimeout(resizeTimer); - resizeTimer = setTimeout(function () { layout(); cur = target = scrollTime(); drawEntry(); draw(cur); }, 120); - } - - // Keyboard: focusing a poster scrolls to the moment Mimi stands beside it. - section.addEventListener("focusin", function (event) { - var i = stops.indexOf(event.target.closest && event.target.closest("[data-stop]")); - var t = i >= 0 ? INTRO + i + 0.66 : end.contains(event.target) ? TOTAL : -1; - if (t < 0) return; - var y = top0 + t / TOTAL * range; - // overflow:hidden still lets focus scroll the stage sideways; undo that. - stage.scrollLeft = 0; stage.scrollTop = 0; - if (Math.abs(window.pageYOffset - y) > 2) window.scrollTo({ top: y, behavior: "instant" }); - cur = target = t; drawEntry(); draw(cur); + stage.appendChild(mimi); + stage.appendChild(say); + stage.appendChild(prog); + measure(); + active = -1; + tick(); + window.addEventListener("scroll", onScroll, { passive: true }); + } + + // Keyboard: Tab to a card scrolls the page to that card's dwell, so the stage and focus agree. + section.querySelector("[data-walk-track]").addEventListener("focusin", function (e) { + var stop = e.target.closest("[data-stop]"); + if (!on || !stop) return; + var i = stops.indexOf(stop); + window.scrollTo(0, Math.round(top + run * (i + DWELL / 2) / N)); }); - layout(); - kick(); - window.addEventListener("scroll", kick, { passive: true }); - window.addEventListener("resize", onResize); - window.addEventListener("load", onResize, { once: true }); - if (document.fonts && document.fonts.ready) document.fonts.ready.then(onResize); - - var imgs = [walkImg]; - function decoded(img) { return img.decode ? img.decode().catch(function () {}) : Promise.resolve(); } - if ("IntersectionObserver" in window) { - var io = new IntersectionObserver(function (entries) { - if (!entries[0].isIntersecting) return; - io.disconnect(); - imgs.forEach(function (img) { img.loading = "eager"; }); - Promise.all(imgs.map(decoded)).then(function () { ready = true; memo = []; drawn = {}; drawEntry(); draw(cur); }); - }, { rootMargin: "100% 0px" }); - io.observe(section); - } else { - ready = true; - } + mode(); + live.addEventListener("change", mode); + var rt = 0; + window.addEventListener("resize", function () { clearTimeout(rt); rt = setTimeout(function () { if (on) { measure(); tick(); } }, 150); }); })(); diff --git a/assets/home.min.css b/assets/home.min.css index b2266e4..8055f45 100644 --- a/assets/home.min.css +++ b/assets/home.min.css @@ -1 +1 @@ -@font-face{font-family:DM Sans;src:url(/assets/fonts/dm-sans-500-latin.woff2) format("woff2");font-style:normal;font-weight:500;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Sans;src:url(/assets/fonts/dm-sans-700-latin.woff2) format("woff2");font-style:normal;font-weight:700;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Gabarito;src:url(/assets/fonts/gabarito-latin.woff2) format("woff2");font-style:normal;font-weight:400 900;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Instrument Sans;src:url(/assets/fonts/instrument-sans-latin.woff2) format("woff2");font-style:normal;font-weight:400 700;font-stretch:75% 100%;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--paper: #f4f4f2;--paper-light: #fbfbfa;--paper-deep: #e8e8e5;--paper-shadow: #dcdcd8;--ink: #171717;--ink-soft: #333331;--muted: #5c5c58;--line: rgba(23, 23, 23, .13);--line-strong: rgba(23, 23, 23, .3);--accent: #2c5a45;--accent-ink: #1f4433;--butter: #e9b949;--sage: #a8bda0;--coral: #cd7c5c;--blush: #e6aaa5;--sky: #a9c3cb;--font-display: "Gabarito", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif;--font-body: "Instrument Sans", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 24px;--space-6: 32px;--space-7: 48px;--space-8: 64px;--space-9: 96px;--space-10: 128px;--radius-s: 10px;--radius-m: 18px;--radius-l: 28px;--radius-pill: 999px;--ease: cubic-bezier(.23, 1, .32, 1);color-scheme:light}*,*:before,*:after{box-sizing:border-box}html{overflow-x:clip;scroll-behavior:smooth;scroll-padding-top:104px;-webkit-text-size-adjust:100%}body{margin:0;overflow-x:clip;background:var(--paper);color:var(--ink);font-family:var(--font-body);font-size:16px;line-height:1.6;font-synthesis:none;-webkit-font-smoothing:antialiased}::selection{background:#cfe0d6;color:var(--ink)}img,picture,canvas{display:block;max-width:100%}a{color:inherit;text-underline-offset:4px}a:focus-visible,button:focus-visible,[tabindex]:focus-visible{outline:3px solid var(--ink);outline-offset:4px;border-radius:4px}h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}.wrap{width:min(1240px,calc(100% - 96px));margin-inline:auto}.skip{position:fixed;top:-100px;left:20px;z-index:100;padding:14px 18px;border-radius:0 0 var(--radius-s) var(--radius-s);background:var(--ink);color:var(--paper);text-decoration:none}.skip:focus{top:0}.dock{position:fixed;top:var(--space-4);left:50%;z-index:50;display:flex;width:min(1240px,calc(100% - 32px));height:64px;align-items:center;justify-content:space-between;gap:var(--space-5);padding:0 var(--space-3) 0 var(--space-4);border:1px solid var(--line);border-radius:var(--radius-pill);background:var(--paper);transform:translate(-50%);transition:box-shadow .25s var(--ease),background-color .25s var(--ease)}.brand{display:inline-flex;flex-shrink:0;align-items:center;gap:10px;color:inherit;text-decoration:none}.brand-num{padding:4px 6px 5px;border:1.5px solid currentColor;border-radius:7px;font-family:var(--font-display);font-size:20px;font-weight:800;letter-spacing:-.06em;line-height:1}.brand-word{font-size:11px;font-weight:600;letter-spacing:0;line-height:1}.dock-nav{display:flex;align-items:center;gap:clamp(14px,2.4vw,30px);margin-left:auto;font-size:14px;font-weight:500}.dock-nav a{padding:10px 2px;text-decoration:none}.dock-nav a:hover{text-decoration:underline;text-decoration-thickness:1px}.dock-actions{display:flex;flex-shrink:0;align-items:center;gap:var(--space-2)}.x-link,.lang-link{display:inline-flex;min-width:40px;height:40px;align-items:center;justify-content:center;border:1px solid var(--line);border-radius:var(--radius-pill);font-size:12px;font-weight:600;line-height:1;text-decoration:none;transition:background-color .2s var(--ease),border-color .2s var(--ease)}.lang-link{min-width:48px}.x-link:hover,.lang-link:hover{border-color:var(--ink);background:var(--paper-deep)}.hero{padding-top:104px;padding-bottom:36px}.hero-top{display:flex;align-items:center;justify-content:space-between;gap:var(--space-5);margin-bottom:clamp(20px,2.4vw,34px)}.kicker{display:inline-flex;align-items:center;gap:10px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:0;line-height:1.5}.kicker-dot{width:7px;height:7px;flex:0 0 auto;border-radius:50%;background:var(--accent)}.latest{display:inline-flex;align-items:center;gap:10px;padding:7px 12px 7px 7px;border:1px solid var(--line-strong);border-radius:var(--radius-pill);background:var(--paper-light);font-size:12px;line-height:1;text-decoration:none;transition:background-color .2s var(--ease),transform .2s var(--ease)}.latest:hover{background:var(--paper-light);transform:translateY(-1px)}.latest-label{padding:5px 9px;border-radius:var(--radius-pill);background:var(--ink);color:var(--paper);font-size:10px;font-weight:600;letter-spacing:0}.latest-name{font-weight:600}.latest time{color:var(--muted);font-variant-numeric:tabular-nums}.latest-arrow{font-size:14px}.hero-stage{position:relative;display:flex;min-height:clamp(540px,calc(100svh - 320px),640px);flex-direction:column;align-items:flex-start;isolation:isolate;cursor:grab;touch-action:pan-y pinch-zoom}.hero-stage.is-dragging{cursor:grabbing}.hero-stage:before{position:absolute;top:-2%;right:-3%;z-index:0;width:min(60vw,760px);aspect-ratio:1;border-radius:50%;display:none;content:"";pointer-events:none}.hero-stage:after{position:absolute;top:8%;right:2.5%;z-index:0;width:min(46vw,580px);aspect-ratio:1;border:1px solid var(--line);border-radius:50%;content:"";pointer-events:none}.wordmark{position:relative;z-index:1;margin:0;padding-top:3px;font-family:var(--font-display);font-weight:800;letter-spacing:-.065em;line-height:.78;text-transform:uppercase;pointer-events:none;-webkit-user-select:none;user-select:none}.wm-207{display:block;font-size:clamp(176px,23.2vw,324px)}.wm-studio{display:block;margin-top:-.015em;padding-left:.055em;font-size:clamp(86px,13.4vw,184px);letter-spacing:-.055em;white-space:nowrap}.wm-dot{color:inherit}.hero-foot{position:relative;z-index:3;display:flex;width:min(100%,500px);flex-direction:column;gap:18px;margin-top:auto;padding-bottom:7px}.intro{max-width:42ch;color:var(--ink-soft);font-size:clamp(16px,1.24vw,18px);line-height:1.55;letter-spacing:-.005em}.hero-actions{display:flex;flex-wrap:wrap;gap:var(--space-3)}.hero-note{color:var(--muted);font-size:11px;letter-spacing:.02em}.stage-mark,.stage-hint{position:absolute;z-index:3;display:flex;align-items:center;gap:8px;color:var(--muted);font-size:9px;font-weight:600;letter-spacing:0;line-height:1.4;pointer-events:none;-webkit-user-select:none;user-select:none}.stage-mark{top:6px;right:2px}.stage-mark b{color:var(--ink);font-weight:600;letter-spacing:.02em}.stage-dot{width:6px;height:6px;border-radius:50%;background:var(--accent)}.stage-hint{right:2px;bottom:10px}.btn{display:inline-flex;min-height:52px;align-items:center;justify-content:center;gap:10px;padding:0 22px;border:1px solid transparent;border-radius:var(--radius-pill);font-family:var(--font-body);font-size:15px;font-weight:600;line-height:1;text-decoration:none;transition:background-color .2s var(--ease),color .2s var(--ease),border-color .2s var(--ease),transform .2s var(--ease)}.btn:hover{transform:translateY(-1px)}.btn-primary{background:var(--ink);color:var(--paper)}.btn-primary:hover{background:#000}.btn-ghost{border-color:var(--line-strong);background:transparent;color:var(--ink)}.btn-ghost:hover{border-color:var(--ink);background:var(--paper-light)}.btn-arrow{font-size:16px;font-weight:400;line-height:1}.mimi{position:absolute;right:13%;bottom:0;z-index:1;height:100%;aspect-ratio:431 / 990;pointer-events:none;translate:var(--mimi-x, 0) var(--mimi-y, 0);transition:translate .25s var(--ease)}.mimi img{display:block;width:100%;height:auto}.mimi:after{position:absolute;bottom:-1%;left:18%;z-index:-1;width:70%;height:4.5%;border-radius:50%;background:radial-gradient(closest-side,#2d241a38,#2d241a00);content:""}@keyframes mimi-float{0%,to{transform:translateY(-1.2%)}50%{transform:translateY(-2.6%)}}@keyframes mimi-shadow{0%,to{opacity:1;transform:scaleX(1)}50%{opacity:.75;transform:scaleX(.92)}}.scene{position:absolute;inset:0;z-index:2;overflow:hidden;pointer-events:none}.scene-poster{position:absolute;inset:0;z-index:2;background-image:url(/assets/clay-scene-poster.webp);background-position:center;background-repeat:no-repeat;background-size:100% 100%;opacity:1;pointer-events:none;transition:opacity .25s var(--ease),visibility .25s}@media(min-width:701px)and (max-width:1196px){.scene-poster{background-image:url(/assets/clay-scene-poster-medium.webp)}}@media(max-width:700px){.scene-poster{background-image:url(/assets/clay-scene-poster-mobile.webp)}}.hero-stage.is-live .scene-poster{visibility:hidden;opacity:0}#clay-scene{position:absolute;inset:0;z-index:1;width:100%;height:100%;color:transparent;font-size:0;line-height:0;touch-action:pan-y pinch-zoom}.facts-strip{display:grid;grid-template-columns:repeat(4,1fr);margin-top:clamp(28px,3.5vw,44px);border-top:1px solid var(--line-strong);border-bottom:1px solid var(--line)}.fact{display:flex;min-width:0;flex-direction:column;gap:4px;padding:var(--space-5) var(--space-4) var(--space-5) 0}.fact+.fact{padding-left:var(--space-5);border-left:1px solid var(--line)}.fact strong{font-family:var(--font-display);font-size:clamp(32px,3.2vw,46px);font-weight:700;letter-spacing:-.04em;line-height:1;font-variant-numeric:tabular-nums}.fact span{color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.4}.section{padding-block:clamp(80px,9vw,128px)}.eyebrow{display:flex;align-items:center;gap:10px;margin-bottom:var(--space-4);color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.5}.eyebrow-index{color:var(--ink);font-variant-numeric:tabular-nums}.section-head{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);align-items:end;gap:clamp(28px,5vw,72px);margin-bottom:clamp(40px,5vw,64px)}.section-head h2{font-family:var(--font-display);font-size:clamp(44px,5.2vw,76px);font-weight:700;letter-spacing:-.05em;line-height:.95}.section-note{max-width:38ch;padding-bottom:6px;color:var(--muted);font-size:15px;line-height:1.6}.category+.category{margin-top:clamp(52px,6vw,84px)}.cat-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--space-5);margin-bottom:var(--space-5);padding-top:var(--space-4);border-top:1px solid var(--ink)}.cat-head h3{display:flex;align-items:baseline;gap:var(--space-3);font-family:var(--font-display);font-size:clamp(26px,2.6vw,36px);font-weight:600;letter-spacing:-.025em;line-height:1.05}.count{color:var(--muted);font-size:.58em;font-weight:500;letter-spacing:.02em;font-variant-numeric:tabular-nums}.cat-head p{margin-top:6px;color:var(--muted);font-size:14px}.cat-note{flex:0 0 auto;padding:6px 10px;border:1px solid var(--line);border-radius:var(--radius-pill);color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1}.cards{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--space-5)}.card{display:flex;grid-column:span 4;min-width:0;flex-direction:column;overflow:hidden;border:1px solid var(--line);border-radius:var(--radius-m);background:var(--paper-light);color:inherit;text-decoration:none;transition:transform .25s var(--ease),border-color .25s var(--ease),box-shadow .25s var(--ease)}.card-wide{grid-column:span 6}.card:hover{border-color:var(--line-strong);box-shadow:0 14px 30px #1d1b1712;transform:translateY(-4px)}.card-media{position:relative;aspect-ratio:16 / 9;overflow:hidden;border-bottom:1px solid var(--line);background:var(--paper-deep)}.card-media img{width:100%;height:100%;object-fit:cover;transition:transform .25s var(--ease)}.card:hover .card-media img{transform:scale(1.035)}.card-badge{position:absolute;top:var(--space-3);left:var(--space-3);z-index:2;padding:6px 10px;border-radius:var(--radius-pill);background:var(--butter);color:var(--ink);font-size:9px;font-weight:700;letter-spacing:0;line-height:1}.card-body{display:flex;flex:1;flex-direction:column;padding:var(--space-4) var(--space-4) var(--space-5)}.card-meta{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);margin-bottom:var(--space-3);color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.4}.card-meta time{flex:0 0 auto;font-variant-numeric:tabular-nums}.card h4{margin-bottom:var(--space-2);font-family:var(--font-display);font-size:clamp(21px,1.8vw,26px);font-weight:600;letter-spacing:-.025em;line-height:1.1;overflow-wrap:break-word}.card-wide h4{font-size:clamp(24px,2.2vw,32px)}.card p{margin-bottom:var(--space-5);color:var(--muted);font-size:14px;line-height:1.5}.card-go{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);margin-top:auto;padding-top:var(--space-3);border-top:1px solid var(--line);font-size:11px;font-weight:600;letter-spacing:0}.workshop{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);margin-top:var(--space-7);padding-top:var(--space-5);border-top:1px solid var(--line);color:var(--muted);font-size:13px}.workshop strong{color:var(--ink-soft);font-weight:600}.text-link{flex:0 0 auto;color:var(--ink);font-size:12px;font-weight:600;letter-spacing:0;text-decoration:none}.text-link:hover{text-decoration:underline;text-underline-offset:4px}.studio{padding-block:clamp(80px,9vw,128px);background:var(--ink);color:var(--paper)}.studio .eyebrow{color:var(--paper-shadow)}.studio .eyebrow-index{color:var(--paper)}.studio-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(36px,6vw,96px)}.studio h2{max-width:12ch;font-family:var(--font-display);font-size:clamp(42px,4.9vw,72px);font-weight:700;letter-spacing:-.05em;line-height:.97}.studio .soft{color:#b9b1a2}.studio-copy{padding-top:4px}.studio-lead{max-width:30ch;margin-bottom:var(--space-5);color:var(--paper);font-size:clamp(20px,1.9vw,26px);line-height:1.45;letter-spacing:-.015em}.studio-copy>p:not(.studio-lead){max-width:52ch;color:#c8c1b3;font-size:15px;line-height:1.7}.studio-stamp{display:inline-flex;align-items:center;gap:var(--space-3);margin-top:var(--space-7);padding:var(--space-3) var(--space-4);border:1px dashed rgba(242,237,227,.35);border-radius:var(--radius-pill);transform:rotate(-3deg)}.stamp-207{font-family:var(--font-display);font-size:26px;font-weight:800;letter-spacing:-.05em;line-height:1}.stamp-meta{color:#b9b1a2;font-size:9px;font-weight:600;letter-spacing:0;line-height:1.45}.facts{margin-top:var(--space-6);border-top:1px solid rgba(242,237,227,.2)}.facts>div{display:grid;grid-template-columns:132px minmax(0,1fr);gap:var(--space-4);padding:13px 0;border-bottom:1px solid rgba(242,237,227,.2)}.facts dt{color:#b9b1a2;font-size:10px;font-weight:600;letter-spacing:0;line-height:1.5}.facts dd{color:var(--paper);font-size:14px;line-height:1.5}.quote{max-width:860px;margin-top:clamp(52px,7vw,88px);padding-top:var(--space-5);border-top:1px solid rgba(242,237,227,.2)}.quote p{margin-bottom:var(--space-4);font-family:var(--font-display);font-size:clamp(24px,2.8vw,38px);font-weight:500;letter-spacing:-.03em;line-height:1.2}.quote cite{color:#b9b1a2;font-size:11px;font-style:normal;font-weight:600;letter-spacing:0}.also{padding-block:clamp(56px,7vw,92px);border-block:1px solid var(--line);background:var(--paper-deep)}.also-inner{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:clamp(24px,4vw,56px)}.also-mark{display:grid;width:88px;height:88px;place-items:center;border-radius:24px;background:var(--paper-deep);box-shadow:5px 6px #1d1b171f;color:var(--ink);font-family:var(--font-display);font-size:44px;font-weight:800;line-height:1;transform:rotate(-4deg)}.also-copy .eyebrow{margin-bottom:var(--space-2)}.also h2{margin-bottom:var(--space-2);font-family:var(--font-display);font-size:clamp(32px,3.4vw,48px);font-weight:700;letter-spacing:-.04em;line-height:1}.also-copy>p:last-child{max-width:56ch;color:var(--muted);font-size:14px;line-height:1.6}.contact{padding-block:clamp(72px,9vw,120px)}.contact-grid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);align-items:end;gap:clamp(32px,6vw,80px)}.contact h2{max-width:13ch;font-family:var(--font-display);font-size:clamp(38px,4.8vw,66px);font-weight:700;letter-spacing:-.05em;line-height:.98}.contact-copy p{max-width:34ch;margin-bottom:var(--space-4);color:var(--muted);font-size:14px}.email{display:inline-block;max-width:100%;padding-bottom:4px;border-bottom:1px solid var(--line-strong);font-family:var(--font-display);font-size:clamp(23px,3vw,40px);font-weight:600;letter-spacing:-.035em;line-height:1.15;overflow-wrap:anywhere;text-decoration:none;transition:border-color .2s var(--ease),color .2s var(--ease)}.email:hover{border-color:var(--accent);color:var(--accent-ink)}.footer{padding-top:var(--space-7);border-top:1px solid var(--line)}.footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:var(--space-6);padding-bottom:clamp(40px,6vw,72px)}.footer-brand p{max-width:26ch;margin-top:var(--space-4);color:var(--muted);font-size:13px;line-height:1.6}.footer-col h2{margin-bottom:var(--space-2);color:var(--muted);font-family:var(--font-body);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.5}.footer-col a{display:block;width:fit-content;padding:4px 0;font-size:13px;line-height:1.5;text-decoration:none}.footer-col a:hover{text-decoration:underline;text-underline-offset:4px}.footer-word{display:block;width:min(100%,760px);height:auto;margin-top:var(--space-4);user-select:none}.footer-bottom{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);padding:var(--space-4) 0 var(--space-6);border-top:1px solid var(--line);color:var(--muted);font-size:11px;letter-spacing:.03em}.footer-bottom a{text-decoration:none}.footer-bottom a:hover{text-decoration:underline}html:lang(ko) body{word-break:keep-all}html:lang(ko) .wm-207,html:lang(ko) .wm-studio{font-family:Gabarito,Apple SD Gothic Neo,Malgun Gothic,sans-serif}html:lang(ko) .section-head h2,html:lang(ko) .studio h2,html:lang(ko) .contact h2{letter-spacing:-.06em;line-height:1.08}html:lang(ko) .card h4,html:lang(ko) .also h2,html:lang(ko) .quote p{letter-spacing:-.03em}html:lang(ko) .kicker,html:lang(ko) .latest-label,html:lang(ko) .card-meta,html:lang(ko) .cat-note,html:lang(ko) .fact span,html:lang(ko) .eyebrow{letter-spacing:.04em}@keyframes reveal-in{0%{translate:0 24px}to{translate:0 0}}@supports (animation-timeline: view()){.reveal{animation:reveal-in .7s var(--ease) both;animation-timeline:view();animation-range:entry 2% entry 24%}}@media(max-width:1080px){.wrap{width:calc(100% - 64px)}.card{grid-column:span 6}.cards .card:nth-child(n+3){grid-column:span 4}.cards-classroom .card{grid-column:span 6}}@media(max-width:900px){.hero{padding-top:104px}.hero-stage{min-height:600px}.section-head,.studio-grid,.contact-grid{grid-template-columns:1fr;gap:var(--space-6)}.section-note{max-width:52ch;padding-bottom:0}.studio h2{max-width:15ch}.studio-copy{max-width:62ch}.also-inner{grid-template-columns:auto minmax(0,1fr)}.also-inner>.btn{grid-column:2;justify-self:start}.footer-grid{grid-template-columns:1.5fr 1fr 1fr}.footer-brand{grid-column:1 / -1}}@media(max-width:860px){.cards .card,.cards .card:nth-child(n+3),.cards-classroom .card{grid-column:span 6}}@media(max-width:700px){.dock{top:10px;width:calc(100% - 24px);height:56px;gap:var(--space-2);padding:0 var(--space-2) 0 var(--space-3)}.brand-num{font-size:17px}.brand-word{display:none}.dock-nav{gap:14px;font-size:12px}.dock-nav .nav-opt{display:none}.x-link,.lang-link{min-width:36px;height:36px}.lang-link{min-width:42px}.wrap{width:calc(100% - 40px)}.hero{padding-top:78px;padding-bottom:var(--space-5)}.hero-top{align-items:flex-start;flex-direction:column;gap:var(--space-3);margin-bottom:var(--space-4)}.kicker{font-size:9px;letter-spacing:.08em}.latest{gap:8px;font-size:11px}.latest time{display:none}.hero-stage{min-height:600px}.mimi{top:78px;right:4%;bottom:auto;height:320px}.hero-stage:before{top:-4%;right:-5vw;width:110vw}.hero-stage:after{top:6%;right:-4vw;width:90vw}.wm-207{font-size:clamp(112px,30vw,132px)}.wm-studio{font-size:clamp(54px,15.2vw,68px)}.wordmark{padding-top:0}.hero-foot{width:100%;gap:14px;padding-bottom:4px}.intro{font-size:15.5px;line-height:1.5}.hero-actions{gap:var(--space-2)}.btn{min-height:48px;padding:0 16px;font-size:13px}.hero-note{font-size:10.5px}.stage-mark{top:2px;right:0;font-size:8px;letter-spacing:.08em}.stage-hint{display:none}.facts-strip{grid-template-columns:1fr 1fr;margin-top:var(--space-6)}.fact{padding:var(--space-4) var(--space-3) var(--space-4) 0}.fact+.fact{padding-left:var(--space-3)}.fact:nth-child(3){padding-left:0;border-left:0;border-top:1px solid var(--line)}.fact:nth-child(4){border-top:1px solid var(--line)}.fact strong{font-size:32px}.fact span{font-size:9px}.section{padding-block:var(--space-8)}.section-head h2{font-size:clamp(38px,11vw,52px)}.section-note{font-size:14px}.cat-head{align-items:flex-start;flex-direction:column;gap:var(--space-3)}.cat-head h3{font-size:26px}.cat-note{align-self:flex-start}.cards{grid-template-columns:1fr;gap:var(--space-4)}.cards .card,.cards .card-wide,.cards .card:nth-child(n+3),.cards-classroom .card{grid-column:1 / -1}.card h4,.card-wide h4{font-size:23px}.card-body{padding:var(--space-4)}.workshop{align-items:flex-start;flex-direction:column;gap:var(--space-3);font-size:12px}.studio{padding-block:var(--space-8)}.studio h2{font-size:clamp(36px,10.5vw,50px)}.studio-lead{font-size:20px}.studio-stamp{margin-top:var(--space-5)}.facts>div{grid-template-columns:96px minmax(0,1fr);gap:var(--space-3)}.quote{margin-top:var(--space-7)}.also{padding-block:var(--space-7)}.also-inner{grid-template-columns:1fr;gap:var(--space-4)}.also-mark{width:64px;height:64px;border-radius:18px;font-size:32px}.also-inner>.btn{grid-column:auto}.contact{padding-block:var(--space-8)}.contact h2{font-size:clamp(32px,9.5vw,44px)}.email{font-size:clamp(21px,6.2vw,28px)}.footer{padding-top:var(--space-6)}.footer-grid{grid-template-columns:1fr 1fr;gap:var(--space-5);padding-bottom:var(--space-7)}.footer-brand{grid-column:1 / -1}.footer-brand .brand-word{display:inline}.footer-word{width:100%;margin-top:var(--space-3)}.footer-bottom{align-items:flex-start;flex-direction:column;gap:var(--space-2);padding-bottom:var(--space-5)}}@media(max-width:380px){.wm-207{font-size:108px}.wm-studio{font-size:52px}.btn{padding-inline:13px;font-size:12px}.dock-nav{gap:10px}}:lang(ko) body,:lang(ko) p,:lang(ko) h1,:lang(ko) h2,:lang(ko) h3,:lang(ko) h4{word-break:keep-all}h1,h2,h3{text-wrap:balance}p,dd,.row-desc{text-wrap:pretty}.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn-primary:hover{background:var(--accent-ink);border-color:var(--accent-ink)}.brand,.email,.dock-nav a,.x-link,.lang-link,.footer-col a,.footer-bottom a,.text-link,.latest,.btn{min-height:44px}.email,.dock-nav a,.footer-col a,.footer-bottom a,.text-link{display:inline-flex;align-items:center}a:focus-visible,button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}html{touch-action:manipulation}.dock-nav a,.footer-col a{min-width:44px;justify-content:center}.vh{position:absolute;width:1px;height:1px;margin:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.count{font-variant-numeric:tabular-nums}.feature{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:var(--space-6);align-items:end;margin-bottom:var(--space-6);color:inherit;text-decoration:none}.feature .card-media{border-radius:var(--radius-m);overflow:hidden;background:var(--paper-deep)}.feature .card-media img{display:block;width:100%;height:auto}.feature-meta{display:flex;gap:var(--space-3);margin:0 0 var(--space-3);color:var(--muted);font-size:14px;font-variant-numeric:tabular-nums}.feature-meta span{color:var(--accent);font-weight:600}.feature-body p:last-child{margin:0;font-size:clamp(20px,2vw,26px);line-height:1.3;letter-spacing:-.015em}.rows{margin:0;padding:0;list-style:none;border-top:1px solid var(--line-strong)}.rows li{border-bottom:1px solid var(--line)}.row{display:grid;grid-template-columns:minmax(0,15rem) minmax(0,1fr) auto 1.5rem;gap:var(--space-5);align-items:baseline;min-height:56px;padding:var(--space-4) 0;color:inherit;text-decoration:none}.row-name{font-family:var(--font-display);font-size:19px;font-weight:600;letter-spacing:-.01em}.row-desc{color:var(--muted)}.row time{color:var(--muted);font-size:14px;font-variant-numeric:tabular-nums}.row-go{color:var(--muted);text-align:right}@media(hover:hover){.row:hover .row-name,.row:hover .row-go{color:var(--accent)}}@media(max-width:700px){.feature{grid-template-columns:1fr;gap:var(--space-4)}.row{grid-template-columns:minmax(0,1fr) auto;gap:var(--space-1) var(--space-4)}.row-name{grid-column:1}.row time{grid-column:2;grid-row:1}.row-desc{grid-column:1 / -1;font-size:15px}.row-go{display:none}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation:none!important;transition:none!important}.card:hover,.latest:hover,.btn:hover,.card:hover .card-media img{transform:none}}.walk{position:relative;padding-block:clamp(64px,9vw,128px) 0}.walk-head h2{margin:0;font-family:var(--font-display);font-size:clamp(2.25rem,5vw,4rem);font-weight:700;line-height:.98;letter-spacing:-.035em;text-wrap:balance}.walk-note{max-width:46ch;margin:var(--space-4) 0 0;color:var(--muted);font-size:17px;line-height:1.5;text-wrap:pretty}.walk-scroller{position:relative}.walk-stage{position:relative;width:min(1240px,calc(100% - 96px));margin:var(--space-8) auto 0;display:grid;grid-template-columns:minmax(150px,240px) 1fr;gap:var(--space-6);align-items:start}.walk-track{grid-column:2;grid-row:1;margin:0;padding:0;list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--space-4)}.walker{grid-column:1;grid-row:1 / span 2;position:sticky;top:104px}.walker-still{display:block;width:100%;height:auto}.walk-wall,.walker-layer,.walker-floor,.walk-rail{display:none}.walk-end{grid-column:2;padding:var(--space-5) 0 0}.walk-end-t{margin:0;font-family:var(--font-display);font-size:1.4rem;font-weight:700;letter-spacing:-.02em}.walk-end-p{margin:var(--space-2) 0 var(--space-3);color:var(--muted)}.poster{position:relative;display:flex;flex-direction:column;gap:var(--space-3);height:100%;padding:22px 24px 20px;border:1px solid rgba(45,36,26,.12);border-radius:2px;background:linear-gradient(180deg,#fdfcf8,#f8f6f0);color:inherit;text-decoration:none;isolation:isolate}.poster:before{position:absolute;top:-7px;left:50%;width:64px;height:15px;margin-left:-32px;border-radius:1px;background:#d6cbb28c;content:"";transform:rotate(-1.5deg)}.poster:before{opacity:.75;transition:opacity .4s var(--ease)}.poster:after{position:absolute;inset:0;z-index:-1;border-radius:inherit;background:#fffffd;opacity:0;content:"";pointer-events:none;transition:opacity .4s var(--ease)}.poster:hover:after,.poster:focus-visible:after{opacity:1}.poster:hover:before,.poster:focus-visible:before{opacity:1}.poster:focus-visible{outline:3px solid var(--accent);outline-offset:4px}.poster-head,.poster-body{display:flex;flex-direction:column;gap:6px;min-width:0}.poster-tag{display:flex;align-items:baseline;gap:10px;color:var(--muted);font-size:13px;font-weight:500}.poster-n{color:var(--accent);font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.02em}.poster-name{font-family:var(--font-display);font-size:clamp(1.45rem,2.5vw,2.3rem);font-weight:700;line-height:1.04;letter-spacing:-.03em;text-wrap:balance}.poster-desc{max-width:44ch;color:var(--ink-soft);line-height:1.45;text-wrap:pretty}.poster-go{color:var(--accent);font-weight:600}.walk.is-live .walk-stage{position:sticky;top:0;display:block;width:100%;height:100vh;height:100svh;margin:0;overflow:hidden;overflow:clip;contain:layout paint}.walk.is-live .walk-stage:before{position:absolute;left:calc(var(--mx) + var(--mw) * .5);bottom:calc(var(--mb) - 22svh);width:calc(var(--mh) * 2.1);aspect-ratio:1;border-radius:50%;background:radial-gradient(closest-side,var(--paper-light) 0%,rgba(251,251,250,.6) 55%,rgba(251,251,250,0) 100%);content:"";transform:translate(-50%);pointer-events:none}.walk.is-live .walk-wall,.walk.is-live .walk-track{position:absolute;top:0;left:0;display:block;width:0;height:0;margin:0;padding:0;transform-origin:0 0;pointer-events:none}.walk.is-live .walk-wall{z-index:0}.walk.is-live .walk-track{z-index:1}.walk.is-live .wall-face{position:absolute;background:linear-gradient(180deg,#e8e7e259,#e8e7e2d9 70%,#e2e0daf2)}.walk.is-live .wall-ledge{position:absolute;background:#2d241a29}.walk.is-live .wall-seams{position:absolute;display:block;background:linear-gradient(90deg,rgba(45,36,26,.12) 0 1px,transparent 1px) 0 0 / 200px 100% repeat-x;will-change:transform}.walk.is-live .stop{position:absolute;top:0;left:0;width:520px;opacity:0;pointer-events:none;transform-origin:0 0;will-change:transform,opacity}.walk.is-live .stop.is-on{pointer-events:auto}.walk.is-live .poster{height:auto;gap:16px;padding:34px 40px 30px;background:linear-gradient(180deg,#f8f6ef,#f1eee5)}.walk.is-live .poster-head{gap:10px}.walk.is-live .poster-name{font-size:clamp(2.2rem,3.9vw,3.6rem)}.walk.is-live .poster-desc{max-width:30ch;font-size:clamp(19px,1.6vw,23px)}.walk.is-live .poster-go{font-size:17px}.walk.is-live .stop.is-here .poster:after,.walk.is-live .stop.is-here .poster:before{opacity:1}.walk.is-live .walker{position:absolute;top:auto;left:var(--mx);bottom:var(--mb);z-index:3;width:var(--mw);height:var(--mh);opacity:0;pointer-events:none}.walk.is-live .walker-layer{position:absolute;inset:0;display:block;overflow:hidden}.walk.is-live .walker-layer img{display:block;width:auto;height:100%;max-width:none;will-change:transform}.walk.is-live .walker-still{display:none}.walk.is-live .walker-floor{position:absolute;left:12%;bottom:-2.2%;z-index:-1;display:block;width:72%;height:5.5%;border-radius:50%;background:radial-gradient(closest-side,#2d241a42,#2d241a1a 60%,#2d241a00);transform-origin:50% 50%}.walk.is-live .walk-rail{position:absolute;top:0;left:0;z-index:2;display:flex;gap:6px;margin:0;padding:0;list-style:none}.walk.is-live .walk-rail li{position:relative;flex:1;min-width:0;padding-top:12px}.walk.is-live .walk-rail li:before,.walk.is-live .rail-fill{position:absolute;top:0;left:0;width:100%;height:2px;border-radius:2px;content:""}.walk.is-live .walk-rail li:before{background:var(--line)}.walk.is-live .rail-fill{background:var(--ink);transform:scaleX(0);transform-origin:0 50%}.walk.is-live .rail-name{display:block;overflow:hidden;color:var(--muted);font-size:12px;font-weight:500;white-space:nowrap;text-overflow:ellipsis;transition:color .3s var(--ease)}.walk.is-live .walk-rail li.is-here .rail-name{color:var(--ink)}.walk.is-live .walk-end{position:absolute;top:0;left:0;z-index:2;width:420px;padding:0;opacity:0;pointer-events:none;will-change:transform,opacity}.walk.is-live .walk-end.is-on{pointer-events:auto}.walk.is-live .walk-end-t{font-size:clamp(1.9rem,3.4vw,2.9rem);line-height:1;letter-spacing:-.035em}.walk.is-live .walk-end-p{margin:var(--space-3) 0 var(--space-4);font-size:17px}.walk.is-live .walk-stage{--mh: min(580px, calc(100svh - 300px) );--mw: calc(var(--mh) * .535);--mx: calc(87% - var(--mw) * .5);--mb: 24px}@media(max-width:700px){.walk-head h2{font-size:clamp(2rem,9vw,2.6rem)}.walk-note{font-size:16px}.walk-stage{width:calc(100% - 40px);margin-top:var(--space-6);grid-template-columns:1fr}.walk-track{grid-column:1;grid-row:2}.walker{position:static;grid-row:1;width:42%;max-width:170px}.walk-end{grid-column:1}.poster{padding:18px 18px 16px}.walk.is-live .walk-stage{--mh: 320px;--mx: calc(96% - var(--mw));--mb: 20px}.walk.is-live .poster{display:flex;align-items:stretch;gap:6px;padding:14px 16px 12px}.walk.is-live .poster:before{top:-6px;width:48px;height:12px;margin-left:-24px}.walk.is-live .poster-head,.walk.is-live .poster-body{gap:2px}.walk.is-live .poster-tag{font-size:12px}.walk.is-live .poster-name{font-size:1.3rem}.walk.is-live .poster-desc{font-size:15px;line-height:1.4}.walk.is-live .poster-go{font-size:14px}.walk.is-live.is-tight .poster{padding:10px 14px 9px}.walk.is-live.is-tight .poster-name{font-size:1.15rem}.walk.is-live.is-tight .poster-desc{font-size:14px;line-height:1.35}.walk.is-live.is-tight .poster-go{font-size:13px}.walk.is-live .rail-name{display:none}.walk.is-live .walk-rail li{padding-top:0;height:10px}.walk.is-live .walk-end-p{font-size:15px}.walk.is-live.is-list .walk-rail{flex-direction:column;gap:10px}.walk.is-live.is-list .walk-rail li{flex:none;height:auto;padding:0 0 9px}.walk.is-live.is-list .walk-rail li:before,.walk.is-live.is-list .rail-fill{top:auto;bottom:0}.walk.is-live.is-list .rail-name{display:block;font-size:17px;font-weight:600;letter-spacing:-.01em}}:root{--night: #0d1333;--night-2: #141a45;--violet: #1b1747;--gold: #e9c46a;--paper: var(--night);--paper-light: #182052;--paper-deep: #19204d;--paper-shadow: #0a0f29;--ink: #f4ecd8;--ink-soft: #e4dccb;--muted: #b9b4cc;--line: rgba(244, 236, 216, .16);--line-strong: rgba(244, 236, 216, .34);--accent: var(--gold);--accent-ink: #f3d68f;--font-display: "DM Sans", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif;--star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23e9c46a' d='M12 1c.4 5.6 2.6 8 11 11-8.4 3-10.6 5.4-11 11-.4-5.6-2.6-8-11-11 8.4-3 10.6-5.4 11-11z'/%3E%3C/svg%3E");color-scheme:dark}body{background:radial-gradient(1.5px 1.5px at 12% 18%,rgba(233,196,106,.7),transparent 60%),radial-gradient(1px 1px at 68% 42%,rgba(244,236,216,.55),transparent 60%),radial-gradient(1.5px 1.5px at 38% 76%,rgba(233,196,106,.55),transparent 60%),radial-gradient(1px 1px at 88% 88%,rgba(244,236,216,.45),transparent 60%),var(--night);background-size:520px 680px,520px 680px,520px 680px,520px 680px,auto}::selection{background:#e9c46a66;color:#fff}a:focus-visible,button:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--gold);outline-offset:3px}.brand{gap:9px}.brand-star{width:18px;height:18px;flex:none;fill:var(--gold)}.brand-word{font-family:DM Sans,sans-serif;font-size:14px;font-weight:700;letter-spacing:.18em;line-height:1;text-transform:uppercase;color:var(--ink)}.dock{background:var(--night);border-color:#f4ecd829}.footer-brand .brand-star{width:22px;height:22px}.footer-brand .brand-word{font-size:17px}.kicker-dot{width:11px;height:11px;border-radius:0;background:var(--star) center / contain no-repeat}.eyebrow:before{display:inline-block;width:.95em;height:.95em;margin-right:.55em;vertical-align:-.1em;background:var(--star) center / contain no-repeat;content:""}.hero{position:relative;isolation:isolate;overflow:hidden;overflow:clip}.hero-paint{position:absolute;inset:0;z-index:-2}.hero-paint img{width:100%;height:100%;object-fit:cover;object-position:72% 100%}.hero:after{position:absolute;inset:0;z-index:-1;background:linear-gradient(90deg,#0a0e28d6,#0a0e289e 34%,#0a0e2800 60%),linear-gradient(0deg,var(--night) 0%,rgba(13,19,51,0) 16%);content:"";pointer-events:none}.hero-stage{cursor:auto}.hero-stage:before,.hero-stage:after{display:none}.mimi{visibility:hidden}.wordmark{display:flex;flex-wrap:nowrap;gap:0 .5em;margin-top:clamp(24px,6vh,72px);padding-top:0;font-family:DM Sans,sans-serif;font-weight:700;letter-spacing:.14em;line-height:1;color:var(--ink);text-shadow:0 2px 24px rgba(8,10,32,.45);white-space:nowrap}.wm-207,.wm-studio,html:lang(ko) .wm-207,html:lang(ko) .wm-studio{display:inline;margin:0;padding:0;font-family:DM Sans,sans-serif;font-size:clamp(56px,7.2vw,112px);letter-spacing:inherit}.hero-sub{margin-top:clamp(14px,1.6vw,22px);padding-left:.2em;color:#efd9a0;font-family:DM Sans,sans-serif;font-size:clamp(13px,1.25vw,18px);font-weight:500;letter-spacing:.34em;text-transform:uppercase}html:lang(ko) .hero-sub{letter-spacing:.24em}.intro{color:var(--ink-soft)}.hero-note{color:#cfc8dc}.latest{background:#10163cd1}.latest:hover{background:#182052eb}.btn-primary,.btn-primary:hover{color:#121a44}.btn-ghost{background:#0d13338c}.btn-ghost:hover{background:#f4ecd81a}.studio,.contact{position:relative;isolation:isolate}.studio{background:var(--violet);color:var(--ink)}.sec-paint{position:absolute;inset:0;z-index:-2;overflow:hidden}.sec-paint img{width:100%;height:100%;object-fit:cover;object-position:85% 50%}.studio:before,.contact:before{position:absolute;inset:0;z-index:-1;content:"";pointer-events:none}.studio:before{background:linear-gradient(180deg,var(--night) 0%,rgba(20,18,58,.88) 14%,rgba(20,18,58,.86) 86%,var(--night) 100%)}.contact{padding-block:var(--space-9);scroll-margin-top:104px}.contact h2,.contact .email{scroll-margin-top:112px}.footer-col{display:flex;flex-direction:column;align-items:flex-start;gap:2px}.footer-col a{justify-content:flex-start;min-width:0}.contact:before{background:linear-gradient(180deg,var(--night) 0%,rgba(12,16,46,.84) 18%,rgba(12,16,46,.8) 82%,var(--night) 100%)}.paint-roof img{object-position:80% 60%}.studio-stamp{border-color:#e9c46a80}.stamp-207{color:var(--gold)}.quote p,.studio-lead,.facts dd,.studio .eyebrow{color:var(--ink)}.studio .eyebrow-index{color:var(--muted)}.studio .soft{color:#cdc4e2}.also{background:var(--night-2)}.also-mark{background:var(--paper-light);color:var(--gold);box-shadow:5px 6px #00000059}.card-badge{color:#121a44}.footer-word{opacity:.1}.poster{--ink-soft: #3b3654;--muted: #5b5672;--accent: #7a5a12;color:#1b1838}.poster:focus-visible{outline-color:var(--gold)}.walk-paint{display:none}.walk.is-live .walk-paint{position:absolute;inset:0;z-index:-1;display:block;pointer-events:none}.walk.is-live .walk-paint img{width:100%;height:100%;object-fit:cover;object-position:30% 40%}.walk.is-live .walk-stage{background:var(--night)}.walk.is-live .walk-stage:before{background:radial-gradient(closest-side,#e9c46a29,#e9c46a0f 55%,#e9c46a00)}.walk.is-live .wall-face{background:linear-gradient(180deg,#141a4514,#141a4557 70%,#0d133399)}.walk.is-live .wall-ledge{background:#e9c46a66}.walk.is-live .wall-seams{background:linear-gradient(90deg,rgba(233,196,106,.2) 0 1px,transparent 1px) 0 0 / 200px 100% repeat-x}.walk.is-live .poster{background:radial-gradient(120% 90% at 20% 10%,#fffcf0e6,#fffcf000 60%),linear-gradient(175deg,#f6eed8,#ece2c6);box-shadow:0 1px #ffffff80 inset,0 18px 40px #0406148c}.walk.is-live .poster:after{background:linear-gradient(175deg,#fbf5e2,#f3e9cf)}.walk.is-live .poster:before{background:#e9c46a80}.walk.is-live .walker-floor{background:radial-gradient(closest-side,#04061499,#04061440 60%,#04061400)}.walk.is-live .rail-fill{background:var(--gold)}@media(max-width:700px){.dock .brand-word{display:inline;font-size:12px;letter-spacing:.13em}.brand-star{width:16px;height:16px}.hero-paint{bottom:auto;height:640px}.hero-paint img{object-position:80% 100%}.hero:after{background:linear-gradient(180deg,rgba(10,14,40,.88) 0%,rgba(10,14,40,.6) 30%,rgba(10,14,40,0) 46%,rgba(13,19,51,0) 74%,var(--night) 640px)}.wordmark{margin-top:10px}.wm-207,.wm-studio,html:lang(ko) .wm-207,html:lang(ko) .wm-studio{font-size:clamp(36px,10.6vw,64px)}.hero-sub{font-size:12px;letter-spacing:.28em}.sec-paint img{object-position:78% 50%}.contact{padding-block:var(--space-8)}} +@font-face{font-family:DM Sans;src:url(/assets/fonts/dm-sans-500-latin.woff2) format("woff2");font-style:normal;font-weight:500;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:DM Sans;src:url(/assets/fonts/dm-sans-700-latin.woff2) format("woff2");font-style:normal;font-weight:700;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Gabarito;src:url(/assets/fonts/gabarito-latin.woff2) format("woff2");font-style:normal;font-weight:400 900;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Instrument Sans;src:url(/assets/fonts/instrument-sans-latin.woff2) format("woff2");font-style:normal;font-weight:400 700;font-display:swap;unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--paper: #f4f4f2;--paper-light: #fbfbfa;--paper-deep: #e8e8e5;--paper-shadow: #dcdcd8;--ink: #171717;--ink-soft: #333331;--muted: #5c5c58;--line: rgba(23, 23, 23, .13);--line-strong: rgba(23, 23, 23, .3);--accent: #2c5a45;--accent-ink: #1f4433;--butter: #e9b949;--sage: #a8bda0;--coral: #cd7c5c;--blush: #e6aaa5;--sky: #a9c3cb;--font-display: "Gabarito", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif;--font-body: "Instrument Sans", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 24px;--space-6: 32px;--space-7: 48px;--space-8: 64px;--space-9: 96px;--space-10: 128px;--radius-s: 10px;--radius-m: 18px;--radius-l: 28px;--radius-pill: 999px;--ease: cubic-bezier(.23, 1, .32, 1);color-scheme:light}*,*:before,*:after{box-sizing:border-box}html{overflow-x:clip;scroll-behavior:smooth;scroll-padding-top:104px;-webkit-text-size-adjust:100%}body{margin:0;overflow-x:clip;background:var(--paper);color:var(--ink);font-family:var(--font-body);font-size:16px;line-height:1.6;font-synthesis:none;-webkit-font-smoothing:antialiased}::selection{background:#cfe0d6;color:var(--ink)}img,picture,canvas{display:block;max-width:100%}a{color:inherit;text-underline-offset:4px}a:focus-visible,button:focus-visible,[tabindex]:focus-visible{outline:3px solid var(--ink);outline-offset:4px;border-radius:4px}h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin:0}.wrap{width:min(1240px,calc(100% - 96px));margin-inline:auto}.skip{position:fixed;top:-100px;left:20px;z-index:100;padding:14px 18px;border-radius:0 0 var(--radius-s) var(--radius-s);background:var(--ink);color:var(--paper);text-decoration:none}.skip:focus{top:0}.dock{position:fixed;top:var(--space-4);left:50%;z-index:50;display:flex;width:min(1240px,calc(100% - 32px));height:64px;align-items:center;justify-content:space-between;gap:var(--space-5);padding:0 var(--space-3) 0 var(--space-4);border:1px solid var(--line);border-radius:var(--radius-pill);background:var(--paper);transform:translate(-50%);transition:box-shadow .25s var(--ease),background-color .25s var(--ease)}.brand{display:inline-flex;flex-shrink:0;align-items:center;gap:10px;color:inherit;text-decoration:none}.brand-num{padding:4px 6px 5px;border:1.5px solid currentColor;border-radius:7px;font-family:var(--font-display);font-size:20px;font-weight:800;letter-spacing:-.06em;line-height:1}.brand-word{font-size:11px;font-weight:600;letter-spacing:0;line-height:1}.dock-nav{display:flex;align-items:center;gap:clamp(14px,2.4vw,30px);margin-left:auto;font-size:14px;font-weight:500}.dock-nav a{padding:10px 2px;text-decoration:none}.dock-nav a:hover{text-decoration:underline;text-decoration-thickness:1px}.dock-actions{display:flex;flex-shrink:0;align-items:center;gap:var(--space-2)}.x-link,.lang-link{display:inline-flex;min-width:40px;height:40px;align-items:center;justify-content:center;border:1px solid var(--line);border-radius:var(--radius-pill);font-size:12px;font-weight:600;line-height:1;text-decoration:none;transition:background-color .2s var(--ease),border-color .2s var(--ease)}.lang-link{min-width:48px}.x-link:hover,.lang-link:hover{border-color:var(--ink);background:var(--paper-deep)}.hero{padding-top:104px;padding-bottom:36px}.hero-top{display:flex;align-items:center;justify-content:space-between;gap:var(--space-5);margin-bottom:clamp(20px,2.4vw,34px)}.kicker{display:inline-flex;align-items:center;gap:10px;color:var(--muted);font-size:11px;font-weight:600;letter-spacing:0;line-height:1.5}.kicker-dot{width:7px;height:7px;flex:0 0 auto;border-radius:50%;background:var(--accent)}.latest{display:inline-flex;align-items:center;gap:10px;padding:7px 12px 7px 7px;border:1px solid var(--line-strong);border-radius:var(--radius-pill);background:var(--paper-light);font-size:12px;line-height:1;text-decoration:none;transition:background-color .2s var(--ease),transform .2s var(--ease)}.latest:hover{background:var(--paper-light);transform:translateY(-1px)}.latest-label{padding:5px 9px;border-radius:var(--radius-pill);background:var(--ink);color:var(--paper);font-size:10px;font-weight:600;letter-spacing:0}.latest-name{font-weight:600}.latest time{color:var(--muted);font-variant-numeric:tabular-nums}.latest-arrow{font-size:14px}.hero-stage{position:relative;display:flex;min-height:clamp(540px,calc(100svh - 320px),640px);flex-direction:column;align-items:flex-start;isolation:isolate;cursor:grab;touch-action:pan-y pinch-zoom}.hero-stage.is-dragging{cursor:grabbing}.hero-stage:before{position:absolute;top:-2%;right:-3%;z-index:0;width:min(60vw,760px);aspect-ratio:1;border-radius:50%;display:none;content:"";pointer-events:none}.hero-stage:after{position:absolute;top:8%;right:2.5%;z-index:0;width:min(46vw,580px);aspect-ratio:1;border:1px solid var(--line);border-radius:50%;content:"";pointer-events:none}.wordmark{position:relative;z-index:1;margin:0;padding-top:3px;font-family:var(--font-display);font-weight:800;letter-spacing:-.065em;line-height:.78;text-transform:uppercase;pointer-events:none;-webkit-user-select:none;user-select:none}.wm-207{display:block;font-size:clamp(176px,23.2vw,324px)}.wm-studio{display:block;margin-top:-.015em;padding-left:.055em;font-size:clamp(86px,13.4vw,184px);letter-spacing:-.055em;white-space:nowrap}.wm-dot{color:inherit}.hero-foot{position:relative;z-index:3;display:flex;width:min(100%,500px);flex-direction:column;gap:18px;margin-top:auto;padding-bottom:7px}.intro{max-width:42ch;color:var(--ink-soft);font-size:clamp(16px,1.24vw,18px);line-height:1.55;letter-spacing:-.005em}.hero-actions{display:flex;flex-wrap:wrap;gap:var(--space-3)}.hero-note{color:var(--muted);font-size:11px;letter-spacing:.02em}.stage-mark,.stage-hint{position:absolute;z-index:3;display:flex;align-items:center;gap:8px;color:var(--muted);font-size:9px;font-weight:600;letter-spacing:0;line-height:1.4;pointer-events:none;-webkit-user-select:none;user-select:none}.stage-mark{top:6px;right:2px}.stage-mark b{color:var(--ink);font-weight:600;letter-spacing:.02em}.stage-dot{width:6px;height:6px;border-radius:50%;background:var(--accent)}.stage-hint{right:2px;bottom:10px}.btn{display:inline-flex;min-height:52px;align-items:center;justify-content:center;gap:10px;padding:0 22px;border:1px solid transparent;border-radius:var(--radius-pill);font-family:var(--font-body);font-size:15px;font-weight:600;line-height:1;text-decoration:none;transition:background-color .2s var(--ease),color .2s var(--ease),border-color .2s var(--ease),transform .2s var(--ease)}.btn:hover{transform:translateY(-1px)}.btn-primary{background:var(--ink);color:var(--paper)}.btn-primary:hover{background:#000}.btn-ghost{border-color:var(--line-strong);background:transparent;color:var(--ink)}.btn-ghost:hover{border-color:var(--ink);background:var(--paper-light)}.btn-arrow{font-size:16px;font-weight:400;line-height:1}.scene{position:absolute;inset:0;z-index:2;overflow:hidden;pointer-events:none}.scene-poster{position:absolute;inset:0;z-index:2;background-image:url(/assets/clay-scene-poster.webp);background-position:center;background-repeat:no-repeat;background-size:100% 100%;opacity:1;pointer-events:none;transition:opacity .25s var(--ease),visibility .25s}@media(min-width:701px)and (max-width:1196px){.scene-poster{background-image:url(/assets/clay-scene-poster-medium.webp)}}@media(max-width:700px){.scene-poster{background-image:url(/assets/clay-scene-poster-mobile.webp)}}.hero-stage.is-live .scene-poster{visibility:hidden;opacity:0}#clay-scene{position:absolute;inset:0;z-index:1;width:100%;height:100%;color:transparent;font-size:0;line-height:0;touch-action:pan-y pinch-zoom}.facts-strip{display:grid;grid-template-columns:repeat(4,1fr);margin-top:clamp(28px,3.5vw,44px);border-top:1px solid var(--line-strong);border-bottom:1px solid var(--line)}.fact{display:flex;min-width:0;flex-direction:column;gap:4px;padding:var(--space-5) var(--space-4) var(--space-5) 0}.fact+.fact{padding-left:var(--space-5);border-left:1px solid var(--line)}.fact strong{font-family:var(--font-display);font-size:clamp(32px,3.2vw,46px);font-weight:700;letter-spacing:-.04em;line-height:1;font-variant-numeric:tabular-nums}.fact span{color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.4}.section{padding-block:clamp(80px,9vw,128px)}.eyebrow{display:flex;align-items:center;gap:10px;margin-bottom:var(--space-4);color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.5}.eyebrow-index{color:var(--ink);font-variant-numeric:tabular-nums}.section-head{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);align-items:end;gap:clamp(28px,5vw,72px);margin-bottom:clamp(40px,5vw,64px)}.section-head h2{font-family:var(--font-display);font-size:clamp(44px,5.2vw,76px);font-weight:700;letter-spacing:-.05em;line-height:.95}.section-note{max-width:38ch;padding-bottom:6px;color:var(--muted);font-size:15px;line-height:1.6}.category+.category{margin-top:clamp(52px,6vw,84px)}.cat-head{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--space-5);margin-bottom:var(--space-5);padding-top:var(--space-4);border-top:1px solid var(--ink)}.cat-head h3{display:flex;align-items:baseline;gap:var(--space-3);font-family:var(--font-display);font-size:clamp(26px,2.6vw,36px);font-weight:600;letter-spacing:-.025em;line-height:1.05}.count{color:var(--muted);font-size:.58em;font-weight:500;letter-spacing:.02em;font-variant-numeric:tabular-nums}.cat-head p{margin-top:6px;color:var(--muted);font-size:14px}.cat-note{flex:0 0 auto;padding:6px 10px;border:1px solid var(--line);border-radius:var(--radius-pill);color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1}.cards{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:var(--space-5)}.card{display:flex;grid-column:span 4;min-width:0;flex-direction:column;overflow:hidden;border:1px solid var(--line);border-radius:var(--radius-m);background:var(--paper-light);color:inherit;text-decoration:none;transition:transform .25s var(--ease),border-color .25s var(--ease),box-shadow .25s var(--ease)}.card-wide{grid-column:span 6}.card:hover{border-color:var(--line-strong);box-shadow:0 14px 30px #1d1b1712;transform:translateY(-4px)}.card-media{position:relative;aspect-ratio:16 / 9;overflow:hidden;border-bottom:1px solid var(--line);background:var(--paper-deep)}.card-media img{width:100%;height:100%;object-fit:cover;transition:transform .25s var(--ease)}.card:hover .card-media img{transform:scale(1.035)}.card-badge{position:absolute;top:var(--space-3);left:var(--space-3);z-index:2;padding:6px 10px;border-radius:var(--radius-pill);background:var(--butter);color:var(--ink);font-size:9px;font-weight:700;letter-spacing:0;line-height:1}.card-body{display:flex;flex:1;flex-direction:column;padding:var(--space-4) var(--space-4) var(--space-5)}.card-meta{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);margin-bottom:var(--space-3);color:var(--muted);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.4}.card-meta time{flex:0 0 auto;font-variant-numeric:tabular-nums}.card h4{margin-bottom:var(--space-2);font-family:var(--font-display);font-size:clamp(21px,1.8vw,26px);font-weight:600;letter-spacing:-.025em;line-height:1.1;overflow-wrap:break-word}.card-wide h4{font-size:clamp(24px,2.2vw,32px)}.card p{margin-bottom:var(--space-5);color:var(--muted);font-size:14px;line-height:1.5}.card-go{display:flex;align-items:center;justify-content:space-between;gap:var(--space-3);margin-top:auto;padding-top:var(--space-3);border-top:1px solid var(--line);font-size:11px;font-weight:600;letter-spacing:0}.workshop{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);margin-top:var(--space-7);padding-top:var(--space-5);border-top:1px solid var(--line);color:var(--muted);font-size:13px}.workshop strong{color:var(--ink-soft);font-weight:600}.text-link{flex:0 0 auto;color:var(--ink);font-size:12px;font-weight:600;letter-spacing:0;text-decoration:none}.text-link:hover{text-decoration:underline;text-underline-offset:4px}.studio{padding-block:clamp(80px,9vw,128px);background:var(--ink);color:var(--paper)}.studio .eyebrow{color:var(--paper-shadow)}.studio .eyebrow-index{color:var(--paper)}.studio-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(36px,6vw,96px)}.studio h2{max-width:12ch;font-family:var(--font-display);font-size:clamp(42px,4.9vw,72px);font-weight:700;letter-spacing:-.05em;line-height:.97}.studio .soft{color:#b9b1a2}.studio-copy{padding-top:4px}.studio-lead{max-width:30ch;margin-bottom:var(--space-5);color:var(--paper);font-size:clamp(20px,1.9vw,26px);line-height:1.45;letter-spacing:-.015em}.studio-copy>p:not(.studio-lead){max-width:52ch;color:#c8c1b3;font-size:15px;line-height:1.7}.studio-stamp{display:inline-flex;align-items:center;gap:var(--space-3);margin-top:var(--space-7);padding:var(--space-3) var(--space-4);border:1px dashed rgba(242,237,227,.35);border-radius:var(--radius-pill);transform:rotate(-3deg)}.stamp-207{font-family:var(--font-display);font-size:26px;font-weight:800;letter-spacing:-.05em;line-height:1}.stamp-meta{color:#b9b1a2;font-size:9px;font-weight:600;letter-spacing:0;line-height:1.45}.facts{margin-top:var(--space-6);border-top:1px solid rgba(242,237,227,.2)}.facts>div{display:grid;grid-template-columns:132px minmax(0,1fr);gap:var(--space-4);padding:13px 0;border-bottom:1px solid rgba(242,237,227,.2)}.facts dt{color:#b9b1a2;font-size:10px;font-weight:600;letter-spacing:0;line-height:1.5}.facts dd{color:var(--paper);font-size:14px;line-height:1.5}.quote{max-width:860px;margin-top:clamp(52px,7vw,88px);padding-top:var(--space-5);border-top:1px solid rgba(242,237,227,.2)}.quote p{margin-bottom:var(--space-4);font-family:var(--font-display);font-size:clamp(24px,2.8vw,38px);font-weight:500;letter-spacing:-.03em;line-height:1.2}.quote cite{color:#b9b1a2;font-size:11px;font-style:normal;font-weight:600;letter-spacing:0}.also{padding-block:clamp(56px,7vw,92px);border-block:1px solid var(--line);background:var(--paper-deep)}.also-inner{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:clamp(24px,4vw,56px)}.also-mark{display:grid;width:88px;height:88px;place-items:center;border-radius:24px;background:var(--paper-deep);box-shadow:5px 6px #1d1b171f;color:var(--ink);font-family:var(--font-display);font-size:44px;font-weight:800;line-height:1;transform:rotate(-4deg)}.also-copy .eyebrow{margin-bottom:var(--space-2)}.also h2{margin-bottom:var(--space-2);font-family:var(--font-display);font-size:clamp(32px,3.4vw,48px);font-weight:700;letter-spacing:-.04em;line-height:1}.also-copy>p:last-child{max-width:56ch;color:var(--muted);font-size:14px;line-height:1.6}.contact{padding-block:clamp(72px,9vw,120px)}.contact-grid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,.85fr);align-items:end;gap:clamp(32px,6vw,80px)}.contact h2{max-width:13ch;font-family:var(--font-display);font-size:clamp(38px,4.8vw,66px);font-weight:700;letter-spacing:-.05em;line-height:.98}.contact-copy p{max-width:34ch;margin-bottom:var(--space-4);color:var(--muted);font-size:14px}.email{display:inline-block;max-width:100%;padding-bottom:4px;border-bottom:1px solid var(--line-strong);font-family:var(--font-display);font-size:clamp(23px,3vw,40px);font-weight:600;letter-spacing:-.035em;line-height:1.15;overflow-wrap:anywhere;text-decoration:none;transition:border-color .2s var(--ease),color .2s var(--ease)}.email:hover{border-color:var(--accent);color:var(--accent-ink)}.footer{padding-top:var(--space-7);border-top:1px solid var(--line)}.footer-grid{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:var(--space-6);padding-bottom:clamp(40px,6vw,72px)}.footer-brand p{max-width:26ch;margin-top:var(--space-4);color:var(--muted);font-size:13px;line-height:1.6}.footer-col h2{margin-bottom:var(--space-2);color:var(--muted);font-family:var(--font-body);font-size:10px;font-weight:600;letter-spacing:0;line-height:1.5}.footer-col a{display:block;width:fit-content;padding:4px 0;font-size:13px;line-height:1.5;text-decoration:none}.footer-col a:hover{text-decoration:underline;text-underline-offset:4px}.footer-word{display:block;width:min(100%,760px);height:auto;margin-top:var(--space-4);user-select:none}.footer-bottom{display:flex;align-items:center;justify-content:space-between;gap:var(--space-4);padding:var(--space-4) 0 var(--space-6);border-top:1px solid var(--line);color:var(--muted);font-size:11px;letter-spacing:.03em}.footer-bottom a{text-decoration:none}.footer-bottom a:hover{text-decoration:underline}html:lang(ko) body{word-break:keep-all}html:lang(ko) .wm-207,html:lang(ko) .wm-studio{font-family:Gabarito,Apple SD Gothic Neo,Malgun Gothic,sans-serif}html:lang(ko) .section-head h2,html:lang(ko) .studio h2,html:lang(ko) .contact h2{letter-spacing:-.06em;line-height:1.08}html:lang(ko) .card h4,html:lang(ko) .also h2,html:lang(ko) .quote p{letter-spacing:-.03em}html:lang(ko) .kicker,html:lang(ko) .latest-label,html:lang(ko) .card-meta,html:lang(ko) .cat-note,html:lang(ko) .fact span,html:lang(ko) .eyebrow{letter-spacing:.04em}@keyframes reveal-in{0%{translate:0 24px}to{translate:0 0}}@supports (animation-timeline: view()){.reveal{animation:reveal-in .7s var(--ease) both;animation-timeline:view();animation-range:entry 2% entry 24%}}@media(max-width:1080px){.wrap{width:calc(100% - 64px)}.card{grid-column:span 6}.cards .card:nth-child(n+3){grid-column:span 4}.cards-classroom .card{grid-column:span 6}}@media(max-width:900px){.hero{padding-top:104px}.hero-stage{min-height:600px}.section-head,.studio-grid,.contact-grid{grid-template-columns:1fr;gap:var(--space-6)}.section-note{max-width:52ch;padding-bottom:0}.studio h2{max-width:15ch}.studio-copy{max-width:62ch}.also-inner{grid-template-columns:auto minmax(0,1fr)}.also-inner>.btn{grid-column:2;justify-self:start}.footer-grid{grid-template-columns:1.5fr 1fr 1fr}.footer-brand{grid-column:1 / -1}}@media(max-width:860px){.cards .card,.cards .card:nth-child(n+3),.cards-classroom .card{grid-column:span 6}}@media(max-width:700px){.dock{top:10px;width:calc(100% - 24px);height:56px;gap:var(--space-2);padding:0 var(--space-2) 0 var(--space-3)}.brand-num{font-size:17px}.brand-word{display:none}.dock-nav{gap:14px;font-size:12px}.dock-nav .nav-opt{display:none}.x-link,.lang-link{min-width:36px;height:36px}.lang-link{min-width:42px}.wrap{width:calc(100% - 40px)}.hero{padding-top:78px;padding-bottom:var(--space-5)}.hero-top{align-items:flex-start;flex-direction:column;gap:var(--space-3);margin-bottom:var(--space-4)}.kicker{font-size:9px;letter-spacing:.08em}.latest{gap:8px;font-size:11px}.latest time{display:none}.hero-stage{min-height:600px}.hero-stage:before{top:-4%;right:-5vw;width:110vw}.hero-stage:after{top:6%;right:-4vw;width:90vw}.wm-207{font-size:clamp(112px,30vw,132px)}.wm-studio{font-size:clamp(54px,15.2vw,68px)}.wordmark{padding-top:0}.hero-foot{width:100%;gap:14px;padding-bottom:4px}.intro{font-size:15.5px;line-height:1.5}.hero-actions{gap:var(--space-2)}.btn{min-height:48px;padding:0 16px;font-size:13px}.hero-note{font-size:10.5px}.stage-mark{top:2px;right:0;font-size:8px;letter-spacing:.08em}.stage-hint{display:none}.facts-strip{grid-template-columns:1fr 1fr;margin-top:var(--space-6)}.fact{padding:var(--space-4) var(--space-3) var(--space-4) 0}.fact+.fact{padding-left:var(--space-3)}.fact:nth-child(3){padding-left:0;border-left:0;border-top:1px solid var(--line)}.fact:nth-child(4){border-top:1px solid var(--line)}.fact strong{font-size:32px}.fact span{font-size:9px}.section{padding-block:var(--space-8)}.section-head h2{font-size:clamp(38px,11vw,52px)}.section-note{font-size:14px}.cat-head{align-items:flex-start;flex-direction:column;gap:var(--space-3)}.cat-head h3{font-size:26px}.cat-note{align-self:flex-start}.cards{grid-template-columns:1fr;gap:var(--space-4)}.cards .card,.cards .card-wide,.cards .card:nth-child(n+3),.cards-classroom .card{grid-column:1 / -1}.card h4,.card-wide h4{font-size:23px}.card-body{padding:var(--space-4)}.workshop{align-items:flex-start;flex-direction:column;gap:var(--space-3);font-size:12px}.studio{padding-block:var(--space-8)}.studio h2{font-size:clamp(36px,10.5vw,50px)}.studio-lead{font-size:20px}.studio-stamp{margin-top:var(--space-5)}.facts>div{grid-template-columns:96px minmax(0,1fr);gap:var(--space-3)}.quote{margin-top:var(--space-7)}.also{padding-block:var(--space-7)}.also-inner{grid-template-columns:1fr;gap:var(--space-4)}.also-mark{width:64px;height:64px;border-radius:18px;font-size:32px}.also-inner>.btn{grid-column:auto}.contact{padding-block:var(--space-8)}.contact h2{font-size:clamp(32px,9.5vw,44px)}.email{font-size:clamp(21px,6.2vw,28px)}.footer{padding-top:var(--space-6)}.footer-grid{grid-template-columns:1fr 1fr;gap:var(--space-5);padding-bottom:var(--space-7)}.footer-brand{grid-column:1 / -1}.footer-brand .brand-word{display:inline}.footer-word{width:100%;margin-top:var(--space-3)}.footer-bottom{align-items:flex-start;flex-direction:column;gap:var(--space-2);padding-bottom:var(--space-5)}}@media(max-width:380px){.wm-207{font-size:108px}.wm-studio{font-size:52px}.btn{padding-inline:13px;font-size:12px}.dock-nav{gap:10px}}:lang(ko) body,:lang(ko) p,:lang(ko) h1,:lang(ko) h2,:lang(ko) h3,:lang(ko) h4{word-break:keep-all}h1,h2,h3{text-wrap:balance}p,dd,.row-desc{text-wrap:pretty}.btn-primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn-primary:hover{background:var(--accent-ink);border-color:var(--accent-ink)}.brand,.email,.dock-nav a,.x-link,.lang-link,.footer-col a,.footer-bottom a,.text-link,.latest,.btn{min-height:44px}.email,.dock-nav a,.footer-col a,.footer-bottom a,.text-link{display:inline-flex;align-items:center}a:focus-visible,button:focus-visible{outline:2px solid var(--accent);outline-offset:3px}html{touch-action:manipulation}.dock-nav a,.footer-col a{min-width:44px;justify-content:center}.vh{position:absolute;width:1px;height:1px;margin:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.count{font-variant-numeric:tabular-nums}.feature{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:var(--space-6);align-items:end;margin-bottom:var(--space-6);color:inherit;text-decoration:none}.feature .card-media{border-radius:var(--radius-m);overflow:hidden;background:var(--paper-deep)}.feature .card-media img{display:block;width:100%;height:auto}.feature-meta{display:flex;gap:var(--space-3);margin:0 0 var(--space-3);color:var(--muted);font-size:14px;font-variant-numeric:tabular-nums}.feature-meta span{color:var(--accent);font-weight:600}.feature-body p:last-child{margin:0;font-size:clamp(20px,2vw,26px);line-height:1.3;letter-spacing:-.015em}.rows{margin:0;padding:0;list-style:none;border-top:1px solid var(--line-strong)}.rows li{border-bottom:1px solid var(--line)}.row{display:grid;grid-template-columns:minmax(0,15rem) minmax(0,1fr) auto 1.5rem;gap:var(--space-5);align-items:baseline;min-height:56px;padding:var(--space-4) 0;color:inherit;text-decoration:none}.row-name{font-family:var(--font-display);font-size:19px;font-weight:600;letter-spacing:-.01em}.row-desc{color:var(--muted)}.row time{color:var(--muted);font-size:14px;font-variant-numeric:tabular-nums}.row-go{color:var(--muted);text-align:right}@media(hover:hover){.row:hover .row-name,.row:hover .row-go{color:var(--accent)}}@media(max-width:700px){.feature{grid-template-columns:1fr;gap:var(--space-4)}.row{grid-template-columns:minmax(0,1fr) auto;gap:var(--space-1) var(--space-4)}.row-name{grid-column:1}.row time{grid-column:2;grid-row:1}.row-desc{grid-column:1 / -1;font-size:15px}.row-go{display:none}}@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation:none!important;transition:none!important}.card:hover,.latest:hover,.btn:hover,.card:hover .card-media img{transform:none}}.walk{position:relative;padding-block:clamp(64px,9vw,128px) 0}.walk-head h2{margin:0;font-family:var(--font-display);font-size:clamp(2.25rem,5vw,4rem);font-weight:700;line-height:.98;letter-spacing:-.035em;text-wrap:balance}.walk-note{max-width:46ch;margin:var(--space-4) 0 0;color:var(--muted);font-size:17px;line-height:1.5;text-wrap:pretty}.walk-scroller{position:relative}.walk-stage{position:relative;width:min(1240px,calc(100% - 96px));margin:var(--space-8) auto 0}.walk-track{margin:0;padding:0;list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:var(--space-5);align-items:start}.stop{display:flex;flex-direction:column;border-radius:6px;overflow:hidden;box-shadow:0 18px 40px #04061473}.walk-end{width:min(1240px,calc(100% - 96px));margin:0 auto;padding:var(--space-5) 0 0}.walk-end-t{margin:0;font-family:var(--font-display);font-size:1.4rem;font-weight:700;letter-spacing:-.02em}.walk-end-p{margin:var(--space-2) 0 var(--space-3);color:var(--muted)}.poster{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr);grid-template-areas:"icon head" "acts acts" "desc desc" "shot shot";align-items:center;gap:14px 16px;padding:24px 24px 22px;background:linear-gradient(180deg,#fdfcf8,#f6f3ea)}.poster:not(:has(.poster-icon)){grid-template-columns:0 minmax(0,1fr);column-gap:0}.poster:before{position:absolute;top:-1px;left:50%;width:64px;height:15px;margin-left:-32px;border-radius:1px;background:#d6cbb2b3;content:"";transform:rotate(-1.5deg)}.poster:focus-visible{outline:3px solid var(--gold);outline-offset:-3px}.poster-icon{grid-area:icon;width:84px;height:84px;border-radius:19px;box-shadow:0 6px 16px #1b183840}.poster-head{grid-area:head;display:flex;flex-direction:column;gap:4px;min-width:0;margin:0}.poster-tag{display:flex;align-items:baseline;gap:10px;color:var(--muted);font-size:13px;font-weight:500}.poster-n{color:var(--accent);font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.02em}.poster-name{font-family:var(--font-display);font-size:clamp(1.6rem,2.8vw,2.7rem);font-weight:700;line-height:1.04;letter-spacing:-.03em;text-wrap:balance}.poster-acts{grid-area:acts;display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;margin:0}.act{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:44px;padding:0 18px;border:1px solid rgba(27,24,56,.35);border-radius:999px;color:#1b1838;font-size:15px;font-weight:600;text-decoration:none;white-space:nowrap;transition:background-color .2s var(--ease),filter .2s var(--ease)}.act:hover{background:#1b18380f}.act-main{border-color:var(--gold);background:var(--gold)}.act-main:hover{background:var(--gold);filter:brightness(1.06)}.act-req{flex-basis:100%;color:var(--muted);font-size:13px}.poster-desc{grid-area:desc;max-width:52ch;margin:0;color:var(--ink-soft);font-size:16px;line-height:1.45;text-wrap:pretty}.poster-shot{grid-area:shot;display:block;width:100%;height:auto;border-radius:10px;background:var(--night);box-shadow:0 10px 26px #1b183859;object-fit:cover}.note{padding:18px 24px 20px;border-top:1px solid rgba(233,196,106,.28);background:var(--night-2);color:var(--ink);font-size:15px;line-height:1.45}.note-what{margin:0;font-weight:600;text-wrap:pretty}.note-for{margin:8px 0 0;color:var(--muted);font-size:14px;text-wrap:pretty}.note-k{margin-right:6px;color:var(--gold);font-size:12px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}.note-pts{display:grid;gap:6px;margin:12px 0 0;padding:0;list-style:none;color:var(--ink-soft);font-size:14px;text-wrap:pretty}.note-pts li{position:relative;padding-left:16px}.note-pts li:before{position:absolute;top:.62em;left:0;width:6px;height:6px;border-radius:50%;background:var(--gold);content:""}.note-go{display:inline-flex;align-items:center;min-height:44px;margin-top:4px;color:var(--gold);font-weight:600;text-decoration:none}.note-go:hover{text-decoration:underline}@media(max-width:700px){.walk-head h2{font-size:clamp(2rem,9vw,2.6rem)}.walk-note{font-size:16px}.walk-stage{width:calc(100% - 40px);margin-top:var(--space-6)}.walk-end{width:calc(100% - 40px)}.poster{gap:12px;padding:18px 16px 16px}.poster-icon{width:56px;height:56px;border-radius:13px}}@media(prefers-reduced-motion:no-preference)and (min-height:560px){.js .walk{--wall-x: 20px;--mimi-h: 150px}.js .walk-scroller{height:548vh;height:548svh}.js .walk-stage{position:sticky;top:0;display:block;width:100%;height:100vh;height:100svh;margin:0;overflow:clip}.js .walk-track{position:absolute;display:block;perspective:1100px;perspective-origin:50% 45%}.js .walk .stop{position:absolute;top:0;left:0;width:100%;opacity:0;pointer-events:none;will-change:transform,opacity;box-shadow:0 30px 80px #0009}.js .walk .stop.is-on{pointer-events:auto}.walk-mimi{position:absolute;left:calc(var(--wall-x) - 4px);bottom:52px;height:var(--mimi-h);aspect-ratio:162 / 420;background:url(/assets/mimi-walk-front.webp) 0 0 / 1200% 100% no-repeat}.walk-mimi:after{position:absolute;left:12%;right:12%;bottom:-6px;height:12px;border-radius:50%;background:radial-gradient(closest-side,rgba(0,0,0,.55),transparent);content:""}.walk-say{position:absolute;margin:0;padding:12px 16px;border-radius:16px;background:#fdfcf8;color:#1b1838;font-size:15px;font-weight:600;line-height:1.4;box-shadow:0 10px 30px #0006;text-wrap:pretty;word-break:keep-all}.walk-say:before{display:block;margin-bottom:4px;color:#7a5a12;font-size:11px;font-weight:700;letter-spacing:.12em;content:"MIMI"}.walk-say:after{position:absolute;width:16px;height:16px;background:#fdfcf8;content:"";transform:rotate(45deg)}.walk-prog{position:absolute;right:var(--wall-x);bottom:18px;left:var(--wall-x);display:flex;align-items:center;gap:16px;color:var(--muted);font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.02em}.walk-prog-at{color:var(--gold)}.walk-prog-bar{position:relative;flex:1;height:2px;background:#e9c46a38}.walk-prog-fill{position:absolute;inset:0;background:var(--gold);transform:scaleX(0);transform-origin:0 50%;will-change:transform}.walk-prog-dot{position:absolute;top:50%;width:8px;height:8px;margin:-4px 0 0 -4px;border-radius:50%;background:var(--night);box-shadow:0 0 0 2px #e9c46a73}.walk-prog-dot.is-lit{background:var(--gold)}}@media(prefers-reduced-motion:no-preference)and (min-height:560px)and (max-width:900px){.js .walk-track{top:76px;right:var(--wall-x);left:var(--wall-x)}.js .walk .poster{grid-template-areas:"icon head" "desc desc" "shot shot" "acts acts"}.js .walk .note-what,.js .walk .note-for{display:none}.js .walk .note-pts{margin-top:0}.js .walk .note{padding:12px 16px 10px}.walk-say{right:var(--wall-x);bottom:calc(52px + var(--mimi-h) * .42);left:calc(var(--wall-x) + var(--mimi-h) * .386 + 12px)}.walk-say:after{left:-6px;bottom:18px}}@media(prefers-reduced-motion:no-preference)and (min-height:560px)and (max-height:760px)and (max-width:900px){.js .walk{--mimi-h: 110px}.walk-say{bottom:calc(52px + var(--mimi-h) * .12)}.js .walk .note{display:none}.js .walk .poster-shot{max-height:150px}}@media(prefers-reduced-motion:no-preference)and (min-height:560px)and (max-width:700px){.js .walk .poster-shot{max-height:180px}}@media(prefers-reduced-motion:no-preference)and (min-height:900px)and (min-width:701px)and (max-width:900px){.js .walk{--wall-x: 32px;--mimi-h: 190px}.js .walk-track{top:100px}.js .walk .poster-shot{max-height:380px}}@media(prefers-reduced-motion:no-preference)and (min-height:560px)and (min-width:901px){.js .walk{--wall-x: max(48px, calc(50vw - 620px) );--mimi-h: min(540px, calc(100svh - 300px) );--card-w: min(780px, calc(100vw - 2 * var(--wall-x) - 360px), max(560px, calc((100svh - 540px)*1.74 + 56px) ))}.js .walk-track{top:max(96px,calc(50svh - 380px));left:calc(var(--wall-x) + 360px);width:var(--card-w)}.js .walk .poster{grid-template-columns:84px minmax(0,1fr) auto;grid-template-areas:"icon head acts" "icon desc acts" "shot shot shot";row-gap:6px;padding:26px 28px 22px}.js .walk .poster-icon{align-self:start}.js .walk .poster-head{align-self:end}.js .walk .poster-desc{align-self:start;font-size:15px}.js .walk .poster-shot{margin-top:12px}.js .walk .poster:not(:has(.poster-icon)){grid-template-columns:0 minmax(0,1fr) auto}.js .walk .poster-acts{flex-direction:column;align-items:stretch}.js .walk .act-req{text-align:center}.js .walk .poster-shot{max-height:calc(100svh - 480px)}.js .walk .note{display:grid;grid-template-columns:1.05fr 1fr;column-gap:24px;padding:18px 28px 20px}.js .walk .note-what,.js .walk .note-for{grid-column:1}.js .walk .note-pts{grid-column:2;grid-row:1 / span 2;align-content:start;margin:0}.js .walk .note-go{grid-column:2;justify-self:start;margin-top:2px;min-height:32px}.walk-mimi{left:calc(var(--wall-x) + 20px);bottom:56px}.walk-say{left:calc(var(--wall-x) - 36px);bottom:calc(56px + var(--mimi-h) + 18px);width:330px;padding:14px 18px;font-size:17px}.walk-say:after{left:44px;bottom:-7px}.walk-prog{right:var(--wall-x);left:var(--wall-x);bottom:28px}}@media(prefers-reduced-motion:no-preference)and (min-height:560px)and (max-height:820px)and (min-width:901px){.js .walk-track{top:88px}.js .walk .note{display:flex;flex-wrap:wrap;align-items:center;gap:2px 22px;padding:10px 28px}.js .walk .note-what,.js .walk .note-for{display:none}.js .walk .note-pts{display:contents}.js .walk .note-go{min-height:32px;margin:0}.js .walk .poster-shot{max-height:calc(100svh - 420px)}}:root{--night: #0d1333;--night-2: #141a45;--violet: #1b1747;--gold: #e9c46a;--paper: var(--night);--paper-light: #182052;--paper-deep: #19204d;--paper-shadow: #0a0f29;--ink: #f4ecd8;--ink-soft: #e4dccb;--muted: #b9b4cc;--line: rgba(244, 236, 216, .16);--line-strong: rgba(244, 236, 216, .34);--accent: var(--gold);--accent-ink: #f3d68f;--font-display: "DM Sans", "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif;--star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23e9c46a' d='M12 1c.4 5.6 2.6 8 11 11-8.4 3-10.6 5.4-11 11-.4-5.6-2.6-8-11-11 8.4-3 10.6-5.4 11-11z'/%3E%3C/svg%3E");color-scheme:dark}body{background:radial-gradient(1.5px 1.5px at 12% 18%,rgba(233,196,106,.7),transparent 60%),radial-gradient(1px 1px at 68% 42%,rgba(244,236,216,.55),transparent 60%),radial-gradient(1.5px 1.5px at 38% 76%,rgba(233,196,106,.55),transparent 60%),radial-gradient(1px 1px at 88% 88%,rgba(244,236,216,.45),transparent 60%),var(--night);background-size:520px 680px,520px 680px,520px 680px,520px 680px,auto}::selection{background:#e9c46a66;color:#fff}a:focus-visible,button:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--gold);outline-offset:3px}.brand{gap:9px}.brand-star{width:18px;height:18px;flex:none;fill:var(--gold)}.brand-word{font-family:DM Sans,sans-serif;font-size:14px;font-weight:700;letter-spacing:.18em;line-height:1;text-transform:uppercase;color:var(--ink)}.dock{background:var(--night);border-color:#f4ecd829}.footer-brand .brand-star{width:22px;height:22px}.footer-brand .brand-word{font-size:17px}.kicker-dot{width:11px;height:11px;border-radius:0;background:var(--star) center / contain no-repeat}.eyebrow:before{display:inline-block;width:.95em;height:.95em;margin-right:.55em;vertical-align:-.1em;background:var(--star) center / contain no-repeat;content:""}.hero{position:relative;isolation:isolate;overflow:hidden;overflow:clip}.hero-paint{position:absolute;inset:0;z-index:-2}.hero-paint img{width:100%;height:100%;object-fit:cover;object-position:72% 100%}.hero:after{position:absolute;inset:0;z-index:-1;background:linear-gradient(90deg,#0a0e28d6,#0a0e289e 34%,#0a0e2800 60%),linear-gradient(0deg,var(--night) 0%,rgba(13,19,51,0) 16%);content:"";pointer-events:none}.hero-stage{cursor:auto}.hero-stage:before,.hero-stage:after{display:none}.wordmark{display:flex;flex-wrap:nowrap;gap:0 .5em;margin-top:clamp(24px,6vh,72px);padding-top:0;font-family:DM Sans,sans-serif;font-weight:700;letter-spacing:.14em;line-height:1;color:var(--ink);text-shadow:0 2px 24px rgba(8,10,32,.45);white-space:nowrap}.wm-207,.wm-studio,html:lang(ko) .wm-207,html:lang(ko) .wm-studio{display:inline;margin:0;padding:0;font-family:DM Sans,sans-serif;font-size:clamp(56px,7.2vw,112px);letter-spacing:inherit}.hero-sub{margin-top:clamp(14px,1.6vw,22px);padding-left:.2em;color:#efd9a0;font-family:DM Sans,sans-serif;font-size:clamp(13px,1.25vw,18px);font-weight:500;letter-spacing:.34em;text-transform:uppercase}html:lang(ko) .hero-sub{letter-spacing:.24em}.intro{color:var(--ink-soft)}.hero-note{color:#cfc8dc}.latest{background:#10163cd1}.latest:hover{background:#182052eb}.btn-primary,.btn-primary:hover{color:#121a44}.btn-ghost{background:#0d13338c}.btn-ghost:hover{background:#f4ecd81a}.studio,.contact{position:relative;isolation:isolate}.studio{background:var(--violet);color:var(--ink)}.sec-paint{position:absolute;inset:0;z-index:-2;overflow:hidden}.sec-paint img{width:100%;height:100%;object-fit:cover;object-position:85% 50%}.studio:before,.contact:before{position:absolute;inset:0;z-index:-1;content:"";pointer-events:none}.studio:before{background:linear-gradient(180deg,var(--night) 0%,rgba(20,18,58,.88) 14%,rgba(20,18,58,.86) 86%,var(--night) 100%)}.contact{padding-block:var(--space-9);scroll-margin-top:104px}.contact h2,.contact .email{scroll-margin-top:112px}.footer-col{display:flex;flex-direction:column;align-items:flex-start;gap:2px}.footer-col a{justify-content:flex-start;min-width:0}.contact:before{background:linear-gradient(180deg,var(--night) 0%,rgba(12,16,46,.84) 18%,rgba(12,16,46,.8) 82%,var(--night) 100%)}.paint-roof img{object-position:80% 60%}.studio-stamp{border-color:#e9c46a80}.stamp-207{color:var(--gold)}.quote p,.studio-lead,.facts dd,.studio .eyebrow{color:var(--ink)}.studio .eyebrow-index{color:var(--muted)}.studio .soft{color:#cdc4e2}.also{background:var(--night-2)}.also-mark{background:var(--paper-light);color:var(--gold);box-shadow:5px 6px #00000059}.card-badge{color:#121a44}.footer-word{opacity:.1}.poster{--ink-soft: #3b3654;--muted: #5b5672;--accent: #7a5a12;color:#1b1838}@media(max-width:700px){.dock .brand-word{display:inline;font-size:12px;letter-spacing:.13em}.brand-star{width:16px;height:16px}.hero-paint{bottom:auto;height:640px}.hero-paint img{object-position:80% 100%}.hero:after{background:linear-gradient(180deg,rgba(10,14,40,.88) 0%,rgba(10,14,40,.6) 30%,rgba(10,14,40,0) 46%,rgba(13,19,51,0) 74%,var(--night) 640px)}.wordmark{margin-top:10px}.wm-207,.wm-studio,html:lang(ko) .wm-207,html:lang(ko) .wm-studio{font-size:clamp(36px,10.6vw,64px)}.hero-sub{font-size:12px;letter-spacing:.28em}.sec-paint img{object-position:78% 50%}.contact{padding-block:var(--space-8)}} diff --git a/assets/home.min.js b/assets/home.min.js index beef3e8..8085c04 100644 --- a/assets/home.min.js +++ b/assets/home.min.js @@ -1 +1 @@ -(function(){"use strict";var i=document.querySelector("[data-scene]");if(!i)return;var w=window.matchMedia("(prefers-reduced-motion: reduce)"),m=document.querySelector("[data-scene-hint]"),R=document.documentElement.lang==="ko",M=!1,F=!1,o=null,q=null,J=!1;function ie(){m&&(m.textContent=w.matches?R?"\uC815\uC801 \uC7A5\uBA74 \xB7 \uBAA8\uC158 \uC904\uC784":"Static scene \xB7 reduced motion":R?"\uB04C\uC5B4\uC11C \uBC00\uC5B4 \uBCF4\uC138\uC694 \xB7 \uB193\uC73C\uBA74 \uB3CC\uC544\uC640\uC694":"Drag the pieces \xB7 they settle back")}ie();function X(n,d,p,A,Me){n.dispatchEvent(new PointerEvent(d,{bubbles:!0,cancelable:!0,pointerId:p.pointerId,pointerType:p.pointerType,clientX:A,clientY:Me,buttons:1}))}function Q(){if(!(M||F||w.matches)){F=!0;var n=document.createElement("script");n.src="/assets/scene.min.js",n.async=!0,n.onload=function(){if(F=!1,typeof window.initClayScene=="function"&&(M=!0,window.initClayScene(),!(J||!o||!q))){var d=document.getElementById("clay-scene");d&&(X(d,"pointerdown",o.event,o.x,o.y),X(d,"pointermove",q.event,q.x,q.y))}},n.onerror=function(){F=!1,o=null},document.head.appendChild(n)}}function g(){M||F||w.matches||("requestIdleCallback"in window?window.requestIdleCallback(Q,{timeout:1400}):window.setTimeout(Q,450))}function Z(n){M||w.matches||n.target.closest&&n.target.closest("a, button, input, textarea, select, label")||(o={id:n.pointerId,x:n.clientX,y:n.clientY,event:n},q={x:n.clientX,y:n.clientY,event:n},J=!1)}function C(n){if(!(M||w.matches||!o||n.pointerId!==o.id)){q={x:n.clientX,y:n.clientY,event:n};var d=n.clientX-o.x,p=n.clientY-o.y;Math.sqrt(d*d+p*p)<8||Q()}}function O(){M||!o||(J=!0)}w.addEventListener&&w.addEventListener("change",function(n){ie(),n.matches||g()}),i.addEventListener("pointerdown",Z),window.addEventListener("pointermove",C,{passive:!0}),window.addEventListener("pointerup",O),window.addEventListener("pointercancel",O);var L=document.querySelector("[data-mimi]");L&&window.matchMedia("(pointer: fine)").matches&&window.addEventListener("pointermove",function(n){if(!w.matches){var d=n.clientX/window.innerWidth-.5,p=n.clientY/window.innerHeight-.5;L.style.setProperty("--mimi-x",(d*14).toFixed(1)+"px"),L.style.setProperty("--mimi-y",(p*8).toFixed(1)+"px")}},{passive:!0}),document.readyState==="complete"?g():window.addEventListener("load",g,{once:!0})})(),(function(){"use strict";var i=document.querySelector("[data-walk]");if(!i||!window.requestAnimationFrame||window.matchMedia("(prefers-reduced-motion: reduce)").matches)return;var w=i.querySelector("[data-walk-scroller]"),m=i.querySelector("[data-walk-stage]"),R=i.querySelector("[data-walker]"),M=i.querySelector("[data-walk-strip]"),F=i.querySelector("[data-walk-floor]"),o=i.querySelector("[data-walk-end]"),q=i.querySelector("[data-wall]"),J=i.querySelector("[data-wall-face]"),ie=i.querySelector("[data-wall-ledge]"),X=i.querySelector("[data-seam]"),Q=i.querySelector("[data-walk-track]"),g=[].slice.call(i.querySelectorAll("[data-stop]")),Z=[].slice.call(i.querySelectorAll("[data-rail-fill]")),C=Z.map(function(e){return e.parentNode}),O=C.length?C[0].parentNode:null,L=document.querySelector("[data-mimi]"),n=g.length,d=.25,p=.4,A=.92,Me=.44,tt=.88,rt=1-A+p,Re=d+n,Xe=Re+p,$=Xe+.35,Ce=96,ee=162,P=420,Le=12,B=10,k=[7,3,159,405],te=[8,6,420,982],at=2,nt=-2,it=[417,415,412,408,405,409,411,414,411,408,405,409],Be=.5,Ee=.86,oe=20*Math.PI/180,ot=10,x=0,H=0,De=1,h=!1,Se=1,se=0,We=0,Ie=0,Ge=1,D=0,y=0,W=1e3,le=1,qe=0,T=0,ce=0,Y=1,de=0,fe=0,ue=[],f=-1,E=0,Te=0,be=!1,Fe=!1,ve=[],S={};i.classList.add("is-live"),w.style.height=$*Ce+100+"vh",w.style.height=$*Ce+100+"svh";function G(e){return e<0?0:e>1?1:e}function st(e){return 1-Math.pow(1-e,3)}function ze(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function u(e){return Math.round(e)+"px"}function Oe(e){return e<.002?"0":e>.998?"1":e.toFixed(3)}function I(e,t,r,a){var s=ve[t]||(ve[t]={});s[r]!==a&&(s[r]=a,e.style[r]=a)}function Ae(e,t,r,a,s){e.style.left=u(t),e.style.top=u(r),e.style.width=u(a),e.style.height=u(s)}function lt(){if(!L||!L.offsetHeight)return null;for(var e=0,t=0,r=L;r;r=r.offsetParent)e+=r.offsetLeft,t+=r.offsetTop;return{x:e,y:t,w:L.offsetWidth,h:L.offsetHeight}}function me(e){return W/(W+e*qe)}function Pe(e){return D+e*le*me(e)}function _(e){return W*(D-e)/((e-D)*qe-le*W)}function je(){var e,t=0;x=m.clientWidth,H=m.clientHeight,h=x<=700;var r=h?76:92,a=h?12:Math.round(Math.max(24,x*.033));["--mh","--mw","--mx","--mb"].forEach(function(xe){m.style.removeProperty(xe)});var s=lt(),l,v,c;if(s){var b=s.h*(te[3]+1-te[1])/990;l=b*P/(k[3]+1-k[1]);var we=s.y+s.h*(te[3]+1)/990;c=we-l*(k[3]+1)/P;var pe=s.x+s.w*(te[0]+te[2]+1)/2/431,ae=s.x+s.w/2,z=l*ee/P,j=((k[0]+k[2]+1)/2-ee/2)*z/ee;v=ae-z/2+(pe-ae-j)/2}else l=R.offsetHeight||320,c=H-24-l,v=R.offsetLeft;var ne=l*ee/P;De=l,We=H-c-l,m.style.setProperty("--mh",l.toFixed(2)+"px"),m.style.setProperty("--mw",ne.toFixed(2)+"px"),m.style.setProperty("--mx",v.toFixed(2)+"px"),m.style.setProperty("--mb",We.toFixed(2)+"px"),i.classList.toggle("is-above",h),Ie=Math.max(0,H-c-l*(k[3]+1)/P),Ge=Math.max(1,Math.min(Math.max(60,H*.12),Ie));var N=v+at*ne/ee,mt=c+nt*l/P,ye=c+l,ht=H-ye-24,He=h&&ht>=200;i.classList.toggle("is-list",He),fe=h?He?ye+30:ye-6:ye-26;var K,V;h?(K=a,V=x-a,de=1/0):(V=N-Math.max(24,l*.06),K=Math.max(a,V-640),de=N-2);var wt=(V-Ee*K)/(1-Ee);le=Math.cos(oe),qe=Math.sin(oe),D=K,W=(wt-K)*Math.tan(oe),T=(V-K)/(Ee*le),ce=T*1.12,Y=ce/2,i.classList.remove("is-tight");var pt=Math.max(1,me(_(a)));for(e=0;e=r+6||Ye)break;i.classList.add("is-tight")}var et=h?1:.5;m.style.perspective=Math.round(W)+"px",m.style.perspectiveOrigin=u(D)+" "+u(y);var yt="rotateY("+oe.toFixed(5)+"rad)";for([q,Q].forEach(function(xe){xe.style.left=u(D),xe.style.transform=yt}),e=0;e=Xe||e<=0)return B;var t=Ve(e);if(t===Math.round(t))return B;var r=(B+Math.floor((t<0?t*Be:t)*24+1e-6))%Le;return r<0?r+Le:r}function dt(e){var t=Fe?ct(e):B,r=(k[3]-it[t])*De/P;I(M,"wi","transform","translate3d("+(-t*100/Le).toFixed(4)+"%,"+r.toFixed(1)+"px,0)");var a=t===B?.5:Math.abs(Math.cos((t-B)/6*Math.PI));I(F,"fl","transform","scaleX("+(.92+a*.1).toFixed(3)+")")}function he(){var e=window.pageYOffset-(se-Ie);I(R,"wo","opacity",Oe(G(e/Ge)))}function ft(e){var t=(e*ce%Y+Y)%Y;I(X,"sm","transform","translate3d("+t.toFixed(1)+"px,0,0)")}function ut(e,t){for(var r=1,a=0;awe?Pe(c+T):-1e4;ae<-4||c>pe||Pe(c)>x+4?b=0:!h&&c>0&&(b=G((de-ae)/Math.max(1,de-Pe(T)))),b===0&&(c=Math.min(Math.max(c,we-T*1.5),pe)),I(l,v+"t","transform","translate3d("+c.toFixed(1)+"px,0,0)"),I(l,v+"o","opacity",Oe(b)),a===n-1&&t>n-1&&(r=b);var z=b>.5,j=s>=Me&&s.5&&(S.end=N>.5,o.classList.toggle("is-on",S.end))}function re(e){var t=Ve(e);t<0&&(t*=Be),ft(t),ut(e,t),dt(e)}function Ue(e){var t=Math.min(.05,(e-Te)/1e3||.016);Te=e;var r=E-f;Math.abs(r)>1.5?f=E-(r>0?.6:-.6):f+=r*(1-Math.exp(-t/.09)),Math.abs(E-f)<5e-4&&(f=E),re(f),f!==E?requestAnimationFrame(Ue):be=!1}function Je(){if(E=Ke(),he(),f<0){f=E,re(f);return}be||(be=!0,Te=performance.now(),requestAnimationFrame(Ue))}var Qe=0;function ke(){clearTimeout(Qe),Qe=setTimeout(function(){je(),f=E=Ke(),he(),re(f)},120)}i.addEventListener("focusin",function(e){var t=g.indexOf(e.target.closest&&e.target.closest("[data-stop]")),r=t>=0?d+t+.66:o.contains(e.target)?$:-1;if(!(r<0)){var a=se+r/$*Se;m.scrollLeft=0,m.scrollTop=0,Math.abs(window.pageYOffset-a)>2&&window.scrollTo({top:a,behavior:"instant"}),f=E=r,he(),re(f)}}),je(),Je(),window.addEventListener("scroll",Je,{passive:!0}),window.addEventListener("resize",ke),window.addEventListener("load",ke,{once:!0}),document.fonts&&document.fonts.ready&&document.fonts.ready.then(ke);var Ze=[M];function vt(e){return e.decode?e.decode().catch(function(){}):Promise.resolve()}if("IntersectionObserver"in window){var $e=new IntersectionObserver(function(e){e[0].isIntersecting&&($e.disconnect(),Ze.forEach(function(t){t.loading="eager"}),Promise.all(Ze.map(vt)).then(function(){Fe=!0,ve=[],S={},he(),re(f)}))},{rootMargin:"100% 0px"});$e.observe(i)}else Fe=!0})(); +(function(){"use strict";var c=document.querySelector("[data-scene]");if(!c)return;var l=window.matchMedia("(prefers-reduced-motion: reduce)"),M=document.querySelector("[data-scene-hint]"),p=document.documentElement.lang==="ko",o=!1,r=!1,i=null,d=null,L=!1;function v(){M&&(M.textContent=l.matches?p?"\uC815\uC801 \uC7A5\uBA74 \xB7 \uBAA8\uC158 \uC904\uC784":"Static scene \xB7 reduced motion":p?"\uB04C\uC5B4\uC11C \uBC00\uC5B4 \uBCF4\uC138\uC694 \xB7 \uB193\uC73C\uBA74 \uB3CC\uC544\uC640\uC694":"Drag the pieces \xB7 they settle back")}v();function w(e,n,f,S,C){e.dispatchEvent(new PointerEvent(n,{bubbles:!0,cancelable:!0,pointerId:f.pointerId,pointerType:f.pointerType,clientX:S,clientY:C,buttons:1}))}function m(){if(!(o||r||l.matches)){r=!0;var e=document.createElement("script");e.src="/assets/scene.min.js",e.async=!0,e.onload=function(){if(r=!1,typeof window.initClayScene=="function"&&(o=!0,window.initClayScene(),!(L||!i||!d))){var n=document.getElementById("clay-scene");n&&(w(n,"pointerdown",i.event,i.x,i.y),w(n,"pointermove",d.event,d.x,d.y))}},e.onerror=function(){r=!1,i=null},document.head.appendChild(e)}}function h(){o||r||l.matches||("requestIdleCallback"in window?window.requestIdleCallback(m,{timeout:1400}):window.setTimeout(m,450))}function b(e){o||l.matches||e.target.closest&&e.target.closest("a, button, input, textarea, select, label")||(i={id:e.pointerId,x:e.clientX,y:e.clientY,event:e},d={x:e.clientX,y:e.clientY,event:e},L=!1)}function y(e){if(!(o||l.matches||!i||e.pointerId!==i.id)){d={x:e.clientX,y:e.clientY,event:e};var n=e.clientX-i.x,f=e.clientY-i.y;Math.sqrt(n*n+f*f)<8||m()}}function u(){o||!i||(L=!0)}l.addEventListener&&l.addEventListener("change",function(e){v(),e.matches||h()}),c.addEventListener("pointerdown",b),window.addEventListener("pointermove",y,{passive:!0}),window.addEventListener("pointerup",u),window.addEventListener("pointercancel",u),document.readyState==="complete"?h():window.addEventListener("load",h,{once:!0})})(),(function(){var c=document.querySelector("[data-walk]");if(!c)return;var l=window.matchMedia("(prefers-reduced-motion: no-preference) and (min-height: 560px)"),M=c.querySelector("[data-walk-scroller]"),p=c.querySelector("[data-walk-stage]"),o=[].slice.call(c.querySelectorAll("[data-stop]")),r=o.length,i=.55,d=-420,L=-600,v=12,w=0,m=1,h=-1,b=!1,y=!1,u=document.createElement("div");u.className="walk-mimi",u.setAttribute("aria-hidden","true");var e=document.createElement("p");e.className="walk-say",e.setAttribute("aria-hidden","true");var n=document.createElement("div");n.className="walk-prog",n.setAttribute("aria-hidden","true");for(var f="",S=0;S';n.innerHTML='01'+f+"0"+r+"";var C=n.firstChild,I=n.querySelector(".walk-prog-fill"),X=n.querySelectorAll(".walk-prog-dot");function Y(t){return t<.5?4*t*t*t:1-Math.pow(2-2*t,3)/2}function k(){w=M.getBoundingClientRect().top+window.pageYOffset,m=Math.max(1,M.offsetHeight-p.offsetHeight)}function H(t){for(var a=0;a0&&s<1?Math.floor(s*2*v)%v:0;u.style.backgroundPositionX=(N/(v-1)*100).toFixed(2)+"%",e.style.opacity=Math.min(1,Math.abs(s-.5)*2).toFixed(3),I.style.transform="scaleX("+(g/(r-1)).toFixed(4)+")",E!==h&&H(E)}function A(){b||(b=!0,requestAnimationFrame(q))}function F(){if(y=l.matches,window.removeEventListener("scroll",A),!y){[u,e,n].forEach(function(t){t.remove()}),o.forEach(function(t){t.style.transform="",t.style.opacity=""});return}p.appendChild(u),p.appendChild(e),p.appendChild(n),k(),h=-1,q(),window.addEventListener("scroll",A,{passive:!0})}c.querySelector("[data-walk-track]").addEventListener("focusin",function(t){var a=t.target.closest("[data-stop]");if(!(!y||!a)){var s=o.indexOf(a);window.scrollTo(0,Math.round(w+m*(s+i/2)/r))}}),F(),l.addEventListener("change",F);var T=0;window.addEventListener("resize",function(){clearTimeout(T),T=setTimeout(function(){y&&(k(),q())},150)})})(); diff --git a/assets/mimi-640.webp b/assets/mimi-640.webp deleted file mode 100644 index 4b2ea9f..0000000 Binary files a/assets/mimi-640.webp and /dev/null differ diff --git a/assets/mimi.webp b/assets/mimi.webp deleted file mode 100644 index 8a9f249..0000000 Binary files a/assets/mimi.webp and /dev/null differ diff --git a/assets/og-home-207studio.png b/assets/og-home-207studio.png index 7dc22c6..32d4c21 100644 Binary files a/assets/og-home-207studio.png and b/assets/og-home-207studio.png differ diff --git a/assets/pages.css b/assets/pages.css index 4b3a612..757bc30 100644 --- a/assets/pages.css +++ b/assets/pages.css @@ -3,7 +3,7 @@ Those sheets share token names (--bg, --ink, --muted, --line, --lime), so redefining them flips the theme. */ @font-face { font-family: "DM Sans"; src: url("/assets/fonts/dm-sans-500-latin.woff2") format("woff2"); font-weight: 500; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } @font-face { font-family: "DM Sans"; src: url("/assets/fonts/dm-sans-700-latin.woff2") format("woff2"); font-weight: 700; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } -@font-face { font-family: "Instrument Sans"; src: url("/assets/fonts/instrument-sans-latin.woff2") format("woff2"); font-weight: 400 700; font-stretch: 75% 100%; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } +@font-face { font-family: "Instrument Sans"; src: url("/assets/fonts/instrument-sans-latin.woff2") format("woff2"); font-weight: 400 700; font-display: swap; unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } :root { --night: #0d1333; --night-2: #141a45; --gold: #e9c46a; --cream: #f4ecd8; diff --git a/assets/pairy.css b/assets/pairy.css new file mode 100644 index 0000000..a836d84 --- /dev/null +++ b/assets/pairy.css @@ -0,0 +1,54 @@ +/* Pairy page (/pairy/, /ko/pairy/): the walk card grown into a page. Loaded after pages.css. */ +body { margin: 0; font-size: 17px; line-height: 1.6; word-break: keep-all; } +.wrap { width: min(1080px, calc(100% - 48px)); margin: 0 auto; } +.top { margin-bottom: 0; } +.top nav { display: flex; gap: 4px 18px; } +img { max-width: 100%; height: auto; } + +.p-hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 48px; align-items: center; padding: 72px 0 56px; } +.p-icon { display: block; width: 112px; height: 112px; border-radius: 25px; box-shadow: 0 14px 34px rgba(4, 6, 20, 0.55); } +.p-hero h1 { margin: 22px 0 6px; font-size: clamp(2.8rem, 6vw, 4.4rem); line-height: 1; letter-spacing: -0.035em; } +.p-sub { margin: 0; color: var(--gold); font-family: var(--display); font-size: 1.25rem; font-weight: 700; } +.p-lede { max-width: 40ch; margin: 14px 0 0; color: var(--ink); } +.p-acts { display: flex; flex-wrap: wrap; gap: 10px; margin: 26px 0 0; } +.p-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 48px; padding: 0 22px; border: 1px solid rgba(244, 236, 216, 0.4); border-radius: 999px; color: var(--ink); font-weight: 600; text-decoration: none; } +.p-btn:hover { border-color: var(--gold); color: var(--gold); } +.p-btn-main { border-color: var(--gold); background: var(--gold); color: #1b1838; } +.p-btn-main:hover { color: #1b1838; filter: brightness(1.06); } +.p-for { margin: 14px 0 0; color: var(--muted); font-size: 14px; } +.p-shot { border-radius: 14px; box-shadow: 0 30px 70px rgba(0, 0, 0, 0.55); } + +.p-sec { padding: 56px 0; border-top: 1px solid var(--line); } +.p-sec h2 { margin: 0 0 8px; font-size: clamp(1.7rem, 3.2vw, 2.4rem); letter-spacing: -0.02em; } +.p-sec > p { max-width: 62ch; color: var(--muted); } + +.p-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 18px; margin: 28px 0 0; padding: 0; list-style: none; counter-reset: step; } +.p-steps li { position: relative; padding: 22px 22px 20px; border: 1px solid var(--line); border-radius: 10px; background: rgba(20, 26, 69, 0.6); counter-increment: step; } +.p-steps li::before { display: block; margin-bottom: 8px; color: var(--gold); font-family: var(--display); font-size: 13px; font-weight: 700; letter-spacing: 0.08em; content: "0" counter(step); } +.p-steps h3 { margin: 0 0 6px; font-size: 1.1rem; } +.p-steps p { margin: 0; color: var(--muted); font-size: 15px; } + +.p-gallery { display: grid; grid-auto-flow: column; grid-auto-columns: min(560px, 82%); gap: 16px; margin: 28px 0 0; padding: 0 0 14px; overflow-x: auto; list-style: none; scroll-snap-type: x mandatory; scrollbar-color: rgba(233, 196, 106, 0.5) transparent; } +.p-gallery li { scroll-snap-align: start; } +.p-gallery img { display: block; border-radius: 12px; box-shadow: 0 16px 36px rgba(0, 0, 0, 0.45); } + +.p-pts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px 28px; margin: 22px 0 0; padding: 0; list-style: none; } +.p-pts li { position: relative; padding-left: 18px; } +.p-pts li::before { position: absolute; top: 0.68em; left: 0; width: 7px; height: 7px; border-radius: 50%; background: var(--gold); content: ""; } + +.p-mac { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 40px; align-items: start; scroll-margin-top: 96px; } +.p-mac .p-acts { margin-top: 18px; } +.p-facts { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 10px 18px; margin: 0; padding: 22px 24px; border-radius: 10px; background: linear-gradient(175deg, #f6eed8, #ece2c6); color: #1b1838; font-size: 15px; } +.p-facts dt { color: #5b5672; font-weight: 600; } +.p-facts dd { margin: 0; } +.p-facts code { font-size: 12.5px; overflow-wrap: anywhere; } +.p-install { margin: 22px 0 0; padding-left: 20px; color: var(--ink); } +.p-install li + li { margin-top: 6px; } +.p-small { color: var(--muted); font-size: 14px; } +.p-small code { padding: 1px 6px; border-radius: 4px; background: rgba(244, 236, 216, 0.1); } + +@media (max-width: 860px) { + .p-hero, .p-mac { grid-template-columns: 1fr; gap: 28px; } + .p-hero { padding-top: 40px; } + .p-steps, .p-pts { grid-template-columns: 1fr; } +} diff --git a/blog/index.html b/blog/index.html index 7993682..99db0a3 100644 --- a/blog/index.html +++ b/blog/index.html @@ -28,7 +28,7 @@ - +

    Writing

    @@ -37,6 +37,6 @@

    Writing

  3. How to play a PPTX in the browser (without PowerPoint)

    Render a .pptx in the browser without a server or Office. What breaks the animations, three ways to do it, and how to test any viewer in five minutes.

  4. - + diff --git a/blog/play-pptx-in-browser/index.html b/blog/play-pptx-in-browser/index.html index 745af1e..ead85ef 100644 --- a/blog/play-pptx-in-browser/index.html +++ b/blog/play-pptx-in-browser/index.html @@ -26,13 +26,13 @@ - +

    How to play a PPTX in the browser (without PowerPoint)

    -
    PPTX Player SDK diagram: a slide stepped with click 1, click 2, then a morph transition
    A deck steps the way PowerPoint plays it: click 1, click 2, then a morph transition between slides.
    +
    Diagram: a slide stepped with click 1, click 2, then a morph transition
    A deck steps the way PowerPoint plays it: click 1, click 2, then a morph transition between slides.

    TL;DR

    • There are three ways to show a .pptx in a browser: render it client-side, convert it on your server, or embed a hosted viewer.
    • @@ -92,17 +92,15 @@

      What people get wrong

      Free libraries, paid engines, and when each makes sense

      Free options exist and they are real. @web-ppt/viewer-core and pptx-viewer both render PPTX in the browser with click animations, and PptxGenJS is the common choice for the other direction, writing files. Server-side engines such as Aspose.Slides start at $999 according to their pricing page. If you want to see how a browser-native renderer handles the slide tree, @silurus/ooxml is a good read, and the Stack Overflow thread on reading PPTX in JavaScript is still the place where the hard parts get named.

      Pick a free library when you have someone who can maintain it. A viewer is a moving target: PowerPoint adds effects, and every fix lands in your fork. Pick a one-time source licence when you would rather own the engine than the maintenance. The $999 figure is Aspose's own starting price, so check their current tier list before you put it in a budget.

      -

      PPTX Player SDK is our client-side engine. It renders a deck to HTML and SVG and replays entrance, exit and emphasis effects, morph transitions and embedded media, offline, in a normal browser or a WebView. Full source is included, and the licence is one-time: $14 for one developer shipping it inside their own apps, $129 for a company where more than one person uses it, with priority email support. Neither tier lets you redistribute the source itself.

      FAQ

      Can you play a PowerPoint file in a browser with its animations?

      Yes. The engine renders each slide to HTML or SVG and steps through the click sequence. Animations survive when the engine reads the timing tree in the file, which is the part most converters skip.

      Do I need a server or Microsoft Office?

      Not for client-side rendering. The browser reads the .pptx package itself, so there is no conversion service and no Office install.

      Does it work offline, or inside an iPad app?

      Yes. The same static folder runs offline in a browser and in a WKWebView on iOS, iPadOS and macOS, and in Android and Electron WebViews.

      What happens when the file is broken?

      A deck with a stray & in the text is repaired and plays normally. If the XML is still broken, the engine renders what it can and reports an error instead of dropping the slide silently.

      Are the free libraries enough?

      They can be, if you are ready to maintain a fork. Check the animation and morph coverage in each README first, and test them with the five item deck above before you commit.

      -
      Which licence tier do I need?

      Developer at $14 is one developer, and you can ship the player inside your own paid or free apps as bundled code. Commercial at $129 is for a company or organisation where more than one person uses it, and adds priority email support. Both include the full source. Neither lets you share the source itself.

    - - + + diff --git a/index.html b/index.html index 82b6a2e..4c70ee1 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ 207 Studio — Classroom tools and source-included dev kits - + @@ -12,7 +12,7 @@ - + @@ -20,12 +20,13 @@ - + + @@ -42,9 +43,8 @@ 207 Studio
    @@ -64,16 +64,14 @@
    -

    @@ -82,7 +80,7 @@

    An independent studio in South Korea making classroom tools and source-included developer kits.

    Full source included. One-time purchase. No subscriptions.

    @@ -93,147 +91,97 @@

    -

    A short walk

    -

    Walk through
    the work with Mimi.

    -

    Scroll, and she walks you past five of the products, stopping at each one.

    +

    What we make.

    +

    Seven products, one at a time.

    - -
      -
    1. - - 01Latest - PairyCore SDK - A live Mac screen on an iPad, with touch and keyboard coming back. - See details - +
    2. +
      + +

      01On the App StorePairy

      +

      App Store Download for MacApple Silicon · macOS 15+ · Version 1.4.2

      +

      A live Mac screen on your iPad, with touch, keyboard and clipboard going back.

      + Pairy on an iPad showing a Mac desktop, with an iPhone beside it as the remote +
      +
      +

      A remote desktop app for your tablet. See your computer screen and control it with touch, trackpad and keyboard.

      +

      For iPad, iPhone and Vision Pro owners with an Apple silicon Mac.

      +
      • Full HD streaming
      • Touch, trackpad and keyboard input
      • Encrypted connections between your devices
      + More about Pairy +
    3. -
    4. - - 02Developer kit - PPTX Player SDK - Click-by-click PowerPoint playback in the browser. No server, no Office. - See details - +
    5. +
      + +

      02Beta (TestFlight) · iPadChalkieboard

      +

      Chalkieboard site

      +

      A lesson board app for teachers on iPad.

      + Chalkieboard's lesson home on iPad: start a lesson, the timetable, a timer, a student picker and the clock +
      +
      +

      An all-in-one board and lesson app for elementary classes. It is built to cover a teacher's whole lesson day in one app. It collects no personal data during class.

      +

      For elementary school teachers who teach with an iPad.

      +
      • Handwriting on the board, plus subject tools
      • Open lesson files, including PPTX and HWP, right in the app
      • Send the board to the classroom display with AirPlay
      +
    6. -
    7. - - 03Developer kit - Scan-to-Grade - A scanner batch in, a finished Excel gradebook out. - See details - +
    8. +
      +

      03In development · WindowsCheckyBoard

      +

      Check paper exams one question at a time.

      +
      +
      +

      A Windows app for checking paper exams one question at a time. You mark each answer right or wrong. The app handles the work around it.

      +

      For teachers who grade paper exams.

      +
      • Make answer sheets and split scans into a box per student and question
      • Mark one question across the whole class, then move on
      • Scores, item analysis and report cards
      +
    9. -
    10. - - 04Developer kit - OMR Auto-Grader - Reads bubbles, O/X, circled words and ordering marks. - See details - +
    11. +
      +

      04Messenger · working nameCool Kiwi

      +

      A messenger app. More soon.

      +
    12. -
    13. - - 05Classroom tool - Exam + OMR Sheet Builder - Exam paper, OMR sheet, key and explanations from one file. - See details - +
    14. +
      +

      05Coming soon · iPhone, iPad, MacStoneWars

      +

      About StoneWars

      +

      Five in a row, where every stone has a character.

      +
      +
      +

      A five-in-a-row game on a 15×15 board in 3D. Win with exactly five in a row or ten kills.

      +
      • Eight stone classes, each with its own skill
      • Seven worlds that change the board
      • Two players on one device or against the built-in AI, no account
      +
      +
    15. +
    16. +
      +

      06Coming soonDeki Duck

      +

      More soon.

      +
      +
    17. +
    18. +
      +

      07Coming soon · Mac, iOS, Windows, webTtoryeot

      +

      Read HWP files without 한글 installed.

      +
      +
      +

      Ttoryeot (또렷) is our HWP/HWPX document engine, written in Rust. It opens 한글 documents fast and renders them faithfully to PDF or images. View only, no editing.

      +

      For anyone who needs to read 한글 (HWP) files without 한글 installed.

      +
      • Layout checked page by page against 한글 12.30 on real documents
      • Fast and light. Images in an 80-image file take 7 ms, down from 196 ms. Peak memory on a large file fell from about 1 GB to 125 MB
      • One engine for Mac, iOS, Windows and the web (WASM)
      +
    -
    - - - Mimi, the clay character, ready to walk you past the products -
    - -
    -

    That’s five of ten.

    -

    The rest are right below, all with full source.

    - See all ten -
    -
    - -
    -
    -
    -

    Selected work

    -

    Made, tested,
    released.

    -
    -

    Every product below is on sale now with full source. No waitlist.

    -
    - - -

    - - -

    - -
    -

    Also in the workshop: Chalkieboard, CheckyBoard and StoneWars — still in development.

    - All 10 products +
    +

    That’s all seven.

    +

    Pairy is on the App Store. The others are still in the workshop.

    + Say hello
    +
    @@ -305,7 +253,6 @@

    Have a classroom problem worth solving?

    diff --git a/ko/about/index.html b/ko/about/index.html index 3999f9b..9174b37 100644 --- a/ko/about/index.html +++ b/ko/about/index.html @@ -38,29 +38,16 @@
    - +

    207 Studio 소개

    -

    207 Studio는 대한민국의 독립 소프트웨어 스튜디오예요. 수업, 교실 발표, 평가에 쓰는 실용적인 도구를 만들고, 그 뒤에 있는 엔진을 소스 포함 개발자 키트로 팔아요.

    +

    207 Studio는 대한민국의 독립 소프트웨어 스튜디오예요. 수업, 교실 발표, 평가에 쓰는 실용적인 앱과 도구를 만들어요.

    207 Studio는 장유일(Jang Yuil)이 설립했어요.

    -

    파는 것은 모두 1회 구매이고 전체 소스 코드가 포함돼요. 구독은 없어요. Personal 등급은 한 사람용이고, Commercial 등급은 연 매출 $1M 이하 회사나 조직을 대상으로 해요. 그보다 크면 따로 협의하는 Enterprise 라이선스가 있어요.

    -

    무엇을 파나요

    - -

    App Store에서는 Pairy: Connecting Devices를, AI 에이전트용으로는 무료 pptx-mcp 서버를 공개했어요.

    +

    무엇을 만드나요

    +

    App Store에서 Pairy: Connecting Devices를 만날 수 있어요. 초키보드는 TestFlight로 베타 테스트 중이에요. 체키보드와 스톤워즈는 개발 중이에요.

    문의

    지원, 라이선스, Enterprise 문의는 support@207studio.dev로 보내 주세요. 모든 메일을 읽어요.

    정책: 이용약관 · 개인정보 처리방침 · 환불 정책. 코드: github.com/207studio.

    AI 에이전트용: llms.txt · llms-full.txt.

    -
    + diff --git a/ko/blog/index.html b/ko/blog/index.html index c5368c5..fdeedd4 100644 --- a/ko/blog/index.html +++ b/ko/blog/index.html @@ -30,7 +30,7 @@ - +

    글

    @@ -39,6 +39,6 @@

    글

  5. PPTX를 브라우저에서 재생하는 법 (PowerPoint 없이)

    서버도 Office도 없이 .pptx를 브라우저에서 렌더링해요. 애니메이션이 깨지는 이유, 세 가지 방법, 뷰어를 5분 만에 확인하는 법을 다뤄요.

    영어 글
  6. - + diff --git a/ko/index.html b/ko/index.html index 876358f..c315401 100644 --- a/ko/index.html +++ b/ko/index.html @@ -4,7 +4,7 @@ 207 Studio — 교실 도구와 소스 포함 개발자 키트 - + @@ -12,7 +12,7 @@ - + @@ -21,18 +21,19 @@ - + + - + @@ -43,9 +44,8 @@ 207 Studio
    @@ -65,16 +65,14 @@

    독립 소프트웨어 스튜디오 · 대한민국

    - - 최신 작업 - PairyCore SDK - + + App Store + Pairy
    -

    @@ -83,7 +81,7 @@

    대한민국의 독립 스튜디오가 교실 도구와 소스 포함 개발자 키트를 만듭니다.

    전체 소스 포함. 1회 구매. 구독 없음.

    @@ -94,147 +92,97 @@

    -

    함께 걷기

    -

    미미와 함께
    제품을 둘러봐요.

    -

    스크롤하면 미미가 이쪽으로 걸어오면서 제품 다섯 개 앞에 하나씩 멈춰 서요.

    +

    만들고 있는 것들.

    +

    일곱 가지를 하나씩 소개해요.

    - -
      -
    1. - - 01최신작 - PairyCore SDK - iPad에 Mac 화면을 실시간으로 띄우고, 터치와 키보드를 다시 Mac으로 보냅니다. - 자세히 보기 - +
    2. +
      + +

      01App StorePairy

      +

      App Store Mac용 다운로드Apple Silicon · macOS 15 이상 · 버전 1.4.2

      +

      Mac 화면을 iPad에 띄우고, 터치와 키보드, 클립보드를 다시 Mac으로 보냅니다.

      + iPad에 Mac 화면을 띄운 Pairy, 옆의 iPhone은 리모컨 +
      +
      +

      태블릿용 원격 데스크톱 앱이에요. 컴퓨터 화면을 보면서 터치, 트랙패드, 키보드로 조작해요.

      +

      대상 Apple 실리콘 Mac과 iPad, iPhone, Vision Pro를 함께 쓰는 사람.

      +
      • Full HD 스트리밍
      • 터치, 트랙패드, 키보드 입력
      • 기기 사이 암호화 연결
      + Pairy 자세히 보기 +
    3. -
    4. - - 02개발자 키트 - PPTX Player SDK - 브라우저에서 PowerPoint를 애니메이션까지 재생합니다. 서버도 Office도 필요 없어요. - 자세히 보기 - +
    5. +
      + +

      02베타 (TestFlight) · iPad초키보드

      +

      초키보드 사이트

      +

      선생님을 위한 아이패드 수업 판서 앱.

      + iPad의 초키보드 수업 홈: 수업 시작, 시간표, 타이머, 학생 뽑기, 시계 +
      +
      +

      초등 수업을 위한 올인원 판서·수업 앱이에요. 선생님의 하루 수업을 앱 하나로 해낼 수 있게 만들었어요. 수업 중에는 개인정보를 수집하지 않아요.

      +

      대상 아이패드로 수업하는 초등 선생님.

      +
      • 판서와 교과별 도구
      • PPTX, HWP 같은 수업 파일을 앱에서 바로 열기
      • AirPlay로 교실 화면에 판서 띄우기
      +
    6. -
    7. - - 03개발자 키트 - Scan-to-Grade - 스캔한 답안지 묶음을 넣으면 Excel 성적표가 나옵니다. - 자세히 보기 - +
    8. +
      +

      03개발 중 · Windows체키보드

      +

      종이 시험을 문항별로 모아 보며 채점해요.

      +
      +
      +

      종이 시험을 문항별로 모아 보며 O/X로 체크하는 Windows 앱이에요. 판정은 사람이 하고, 그 앞뒤 일은 프로그램이 맡아요.

      +

      대상 종이 시험을 채점하는 선생님.

      +
      • 답안지를 만들고, 스캔본을 학생별·문항별 칸으로 나눠요
      • 한 문항을 반 전체에서 이어서 채점해요
      • 점수, 문항 분석, 성적 통지표
      +
    9. -
    10. - - 04개발자 키트 - OMR Auto-Grader - 버블, O/X, 동그라미, 순서 표시까지 읽습니다. - 자세히 보기 - +
    11. +
      +

      04메신저 · 가칭쿨 키위

      +

      메신저 앱이에요. 곧 소개할게요.

      +
    12. -
    13. - - 05교실 도구 - Exam + OMR Sheet Builder - 시험지, OMR 답안지, 정답지, 해설지를 한 파일에서. - 자세히 보기 - +
    14. +
      +

      05출시 준비 중 · iPhone, iPad, Mac스톤워즈

      +

      스톤워즈 소개

      +

      돌마다 캐릭터가 있는 오목 게임.

      +
      +
      +

      15×15 입체 보드에서 두는 오목 게임이에요. 정확히 다섯 줄을 만들거나 열 번 처치하면 이겨요.

      +
      • 스킬이 다른 여덟 가지 병과
      • 보드가 달라지는 일곱 가지 세계
      • 한 기기에서 둘이, 또는 내장 AI와 대전. 계정 없이
      +
      +
    15. +
    16. +
      +

      06준비 중데키 덕

      +

      곧 소개할게요.

      +
      +
    17. +
    18. +
      +

      07출시 예정 · Mac, iOS, Windows, 웹또렷

      +

      한글 프로그램 없이 HWP 문서를 열어 봐요.

      +
      +
      +

      또렷은 Rust로 만든 HWP/HWPX 문서 엔진이에요. 한글 문서를 빠르게 열고 원본 모양 그대로 PDF나 이미지로 그려요. 보기 전용이라 편집은 안 돼요.

      +

      대상 한글 프로그램 없이 HWP 파일을 읽어야 하는 사람.

      +
      • 실제 문서로 한글 12.30 출력과 쪽마다 대조한 레이아웃
      • 빠르고 가벼워요. 이미지 80장 문서는 196ms에서 7ms로, 큰 문서의 최대 메모리는 약 1GB에서 125MB로 줄었어요
      • Mac, iOS, Windows, 웹(WASM)을 엔진 하나로
      +
    -
    - - - 제품 사이를 함께 걸어갈 점토 캐릭터 미미 -
    - -
    -

    열 개 중 다섯 개를 봤어요.

    -

    나머지도 바로 아래에 있어요. 모두 소스 코드가 들어 있어요.

    - 10개 모두 보기 -
    -
    - -
    -
    -
    -

    작업

    -

    만들고, 써 보고,
    내놓았습니다.

    -
    -

    아래 제품은 지금 구매할 수 있고, 전체 소스가 함께 옵니다. 대기 명단은 없습니다.

    -
    - - -

    - - -

    - -
    -

    작업실에서는 Chalkieboard, CheckyBoard, StoneWars를 만들고 있습니다. 아직 개발 중입니다.

    - 제품 10개 모두 보기 +
    +

    일곱 개를 다 봤어요.

    +

    Pairy는 App Store에 있어요. 나머지는 아직 작업 중이에요.

    + 인사 건네기
    +
    @@ -306,7 +254,6 @@

    교실의 문제를 함께 풀어 볼까요?

    diff --git a/ko/pairy/index.html b/ko/pairy/index.html new file mode 100644 index 0000000..a518c31 --- /dev/null +++ b/ko/pairy/index.html @@ -0,0 +1,103 @@ + + + + + + + + +Pairy(페어리): 내 Mac을 iPad에서 | 207 Studio + + + + + + + + + + + + + + + + + + + + +
    +
    207 Studio
    +
    +
    +
    + +

    Pairy

    +

    내 컴퓨터를 태블릿에서.

    +

    Pairy는 Mac의 화면과 키보드·트랙패드 조작을 iPad나 iPhone으로 이어 줘요. 화면을 직접 만지고, 입력하고, iPhone을 리모컨으로 써요.

    +

    App Store Mac용 Pairy

    +

    iPad, iPhone, Vision Pro와 Apple 실리콘 Mac을 함께 쓰는 분께.

    +
    + iPad에 Mac 화면을 띄운 Pairy, 옆의 iPhone은 리모컨 +
    + +
    +

    이렇게 써요

    +

    앱 두 개, 계정 하나. iPad·iPhone 앱이 Mac 화면을 보여 주고, Mac 동반 앱이 화면을 보내고 입력을 받아요.

    +
      +
    1. iPad나 iPhone에 Pairy 설치

      App Store에서 받아요.

    2. +
    3. Mac에 Mac용 Pairy 설치

      아래에서 내려받고, macOS가 물으면 화면 녹화와 손쉬운 사용 권한을 허용해요.

    4. +
    5. 같은 Tailscale 계정으로 로그인

      두 기기 모두에서요. Pairy가 내 컴퓨터를 찾고, 여러 대면 이름으로 골라요.

    6. +
    +
    + +
    +

    이렇게 보여요

    + +
      +
    • FHD 화면 전송
    • +
    • 터치 트랙패드와 키보드 입력
    • +
    • 암호화된 내 기기 간 연결
    • +
    • 유화 밤하늘과 간결한 대시보드
    • +
    +
    + +
    +
    +

    Mac용 Pairy

    +

    iPad·iPhone 앱이 연결하는 Mac 동반 앱이에요. 개발자 ID로 서명하고 Apple 공증을 받았어요. 저희에게서 바로 내려받아요.

    +

    Mac용 Pairy 다운로드

    +
      +
    1. zip 파일을 더블클릭해 압축을 풀고, PairyMac.app을 응용 프로그램 폴더로 옮겨요.
    2. +
    3. 앱을 열고, macOS가 물으면 화면 녹화와 손쉬운 사용 권한을 허용해요.
    4. +
    5. Pairy를 쓰는 동안 Mac은 켜 두고 네트워크에 연결해 두세요.
    6. +
    +

    파일을 확인하려면 터미널에서 shasum -a 256 Pairy-mac-1.4.2-142-notarized.zip을 실행해 이 페이지의 SHA-256과 비교해요.

    +
    +
    +
    버전
    1.4.2 (빌드 142)
    +
    요구 사항
    Apple 실리콘(M1 이상), macOS 15 이상
    +
    서명
    개발자 ID, Apple 공증
    +
    파일
    Pairy-mac-1.4.2-142-notarized.zip
    +
    크기
    22.1MB (22,075,699바이트)
    +
    SHA-256
    38584e50e1963fd45369ebe496bce3324515651f1f81946a5248dcca23fc1ded
    +
    +
    + +
    +

    알아 두세요

    +

    Tailscale 계정이 필요하고, 해당 서비스의 약관과 요금제가 별도로 적용돼요. 실제 화질과 반응 속도는 기기와 네트워크에 따라 달라져요. 문의: support@207studio.dev

    +
    +
    +
    + + + diff --git a/ko/products/classroom-html-tools/index.html b/ko/products/classroom-html-tools/index.html index 5d20f50..a46d817 100644 --- a/ko/products/classroom-html-tools/index.html +++ b/ko/products/classroom-html-tools/index.html @@ -3,62 +3,15 @@ - - - - -Offline Classroom Tools: 자리 배치, PACER 타이머 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Offline Classroom Tools: 자리 배치, PACER 타이머

    -

    HTML 파일 하나로 도는 교실 도구 네 가지예요. 자리 배치와 출석, 주간 출석 Excel을 내보내는 아침 게시판, 체력 측정 기록, Wi-Fi QR 카드가 들어 있어요.

    - -Offline Classroom Tools: 자리 배치, PACER 타이머 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Teacher $9 · School $19

    -

    1회 구매.

    - - -

    무엇인가요

    Offline Classroom Tools는 더블클릭으로 여는 교실 도구 네 가지예요. 끌어서 배치하고 출석도 표시하는 자리 배치, 주간 출석 기록을 Excel로 내보내는 아침 게시판, PACER 왕복 달리기 타이머와 체력 측정 기록, 인쇄할 수 있는 Wi-Fi QR 카드가 있어요. 설치도 계정도 서버도 필요 없고, 데이터는 브라우저 안에 남아요.

    -

    빠르게 시작하기

    체력 측정 기록을 열고 시작을 눌러요
    -예시 명단을 학급 게시판에 붙여 넣어요
    -JSON 백업을 내보내요
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    TpT Smart Seating Chart GeneratorHTML 파일 하나$5.00, 자리 배치만
    GOGO PACER tool무료 웹 앱학생별 탈락 기록이 되는 PACER 타이머, 온라인
    RunTrak체력 측정 앱1회 구매
    Offline Classroom Tools오프라인 HTML 파일 네 개, 소스 포함1회 $9, 자리 배치·출석 Excel·PACER 기록·Wi-Fi QR 카드
    -

    자주 묻는 질문

    -
    결과를 기록하는 오프라인 PACER 왕복 달리기 타이머가 있나요?

    네. 체력 측정 기록에 PACER 비프 타이머가 있고, 앉아 윗몸 앞으로 굽히기, 윗몸 일으키기, 멀리뛰기, 키와 몸무게를 기록해요. CSV 가져오기와 내보내기도 돼요.

    매일 출석을 Excel로 내보낼 수 있나요?

    네. 아침 게시판이 출석 기록을 Excel로 내보내요. 주마다 시트 하나예요.

    계정이나 인터넷이 필요한가요?

    아니요. 도구마다 HTML 파일 하나예요. 데이터는 JSON 백업과 함께 브라우저 안에 남아요.

    Wi-Fi QR 카드는 어떻게 쓰나요?

    학생용과 교직원용 네트워크 카드를 인쇄해요. 휴대폰으로 스캔해서 접속해요. 이름과 비밀번호는 그 자리에서 고칠 수 있어요.

    구독인가요?

    아니요. 1회 구매이고 전체 소스가 포함돼요.

    어떤 등급을 사야 하나요?

    Teacher는 선생님 한 명이 자기 수업에서 쓰거나 직접 만든 유료·무료 제품 안에 넣는 라이선스예요. School은 한 학교의 모든 교사와 직원이 쓸 수 있고 우선 이메일 지원이 붙어요. 두 등급 모두 도구 네 가지가 모두 들어 있어요. 1회 구매예요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Teacher $9 · School $19

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/exam-sheet-builder/index.html b/ko/products/exam-sheet-builder/index.html index cd42a08..a46d817 100644 --- a/ko/products/exam-sheet-builder/index.html +++ b/ko/products/exam-sheet-builder/index.html @@ -3,62 +3,15 @@ - - - - -Exam + OMR Sheet Builder: 시험지, OMR 카드, 정답지 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Exam + OMR Sheet Builder: 시험지, OMR 카드, 정답지

    -

    시험 내용을 한 번 입력하면 시험지, OMR 답안지, 정답지, 해설지, 표지까지 서로 맞는 문서로 인쇄해요.

    - -Exam + OMR Sheet Builder: 시험지, OMR 카드, 정답지 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $12 · Commercial $35

    -

    1회 구매.

    - - -

    무엇인가요

    Exam + OMR Sheet Builder는 시험 내용을 한 번 입력하고 서로 맞는 다섯 문서를 인쇄하는 도구예요. 시험지, OMR 버블 답안지, 정답지, 해설지, 표지가 모두 같은 데이터에서 나와서 서로 어긋나지 않아요. 편집기는 오프라인 HTML 파일 하나이고, MCP 서버로 AI 비서가 시험 초안을 잡을 수도 있어요.

    -

    빠르게 시작하기

    node build.mjs examples/demo-science-en.json out
    -open out/exam.html
    -node build-standalone.mjs
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    EdFleet무료 웹 시험지 제작기시험지
    PuzzlePage무료 웹 생성기버블 답안지
    Sigma Prep MCP무료 MCP 서버AI 학습지 생성
    Exam + OMR Sheet Builder오프라인 파일 하나 + MCP 서버, 소스 포함1회 $12, 시험지·OMR 답안지·정답지·해설지·표지를 한 파일에서
    -

    자주 묻는 질문

    -
    계정이나 설치가 필요한가요?

    아니요. 편집기는 더블클릭으로 여는 HTML 파일 하나예요. 미리 보기와 자동 저장이 있어요.

    어떤 문항 유형을 지원하나요?

    지문, 이미지, 표, 그래프, 선택지 수가 자유로운 객관식, 단답형, 서술형, 빈칸, 순서 문항을 지원해요.

    AI 비서로 시험을 만들 수 있나요?

    네. 들어 있는 MCP 서버로 AI 비서가 같은 렌더러로 시험 초안을 잡고, 검사하고, 렌더링할 수 있어요.

    어떻게 인쇄하나요?

    브라우저에서 A4로 인쇄하고, 인쇄 대화상자에서 PDF로 저장해요.

    시험에 맞는 OMR 답안지를 자동으로 만들 수 있나요?

    네. 시험지, OMR 답안지, 정답지, 해설지, 표지가 모두 시험 파일 하나에서 나와서 문항 번호와 선택지가 서로 맞아요.

    어떤 등급을 사야 하나요?

    Personal은 한 사람용이고, 직접 만든 유료·무료 앱에 컴파일하거나 번들한 코드로 넣을 수 있어요. Commercial은 회사나 조직에서 여러 명이 쓸 때이고 우선 이메일 지원이 붙어요. 두 등급 모두 소스 자체를 공유할 수는 없고, 전체 소스는 포함돼요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $12 · Commercial $35

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/index.html b/ko/products/index.html index 1b49928..a46d817 100644 --- a/ko/products/index.html +++ b/ko/products/index.html @@ -3,112 +3,15 @@ - - - - -제품: 개발자 키트와 교실 도구 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    -

    207 STUDIO / 제품

    제품

    대한민국의 독립 스튜디오가 만든 개발자 키트와 교실 도구예요. 1회 구매이고 전체 소스를 받아요.

    -
    -
    -

    개발자용

    -
    - -
    -
    -
    -
    -
    -

    선생님용

    -
    - -
    -
    -
    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/omr-auto-grader/index.html b/ko/products/omr-auto-grader/index.html index 76e1d0c..a46d817 100644 --- a/ko/products/omr-auto-grader/index.html +++ b/ko/products/omr-auto-grader/index.html @@ -3,62 +3,15 @@ - - - - -OMR Auto-Grader: 버블, O/X, 동그라미 답안 채점 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    OMR Auto-Grader: 버블, O/X, 동그라미 답안 채점

    -

    스캔한 답안지에서 버블과 동그라미, O/X 상자, 순서 표시를 읽고, 확실하게 읽힌 답만 채워요.

    - -OMR Auto-Grader: 버블, O/X, 동그라미 답안 채점 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $59 · Commercial $249

    -

    1회 구매.

    - - -

    무엇인가요

    OMR Auto-Grader는 스캔한 답안지를 위한 Python OMR 엔진이에요. A부터 E까지의 버블을 넘어 복수 답안 표시, 동그라미 친 단어, O/X 상자, 순서 버블도 읽어요. 확실하게 읽힌 답만 성적에 채우고, 선생님이 이미 표시한 답은 덮어쓰지 않아요. 나머지는 다시 확인할 목록으로 보내요. 데모를 실행하면 Excel로 내보내요.

    -

    빠르게 시작하기

    pip install -r requirements.txt
    -python demo.py
    -open out/
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    OMRChecker무료 오픈 소스 (MIT)버블 답안지
    OpenMCR무료 오픈 소스 (GPL-3)버블 답안지
    MarkShark오픈 소스 (AGPL)상용 조건은 문의
    Aspose.OMRSDK$599부터 (Small Business $839, OEM $2,517)
    LEADTOOLS OMRSDK$795
    OMR Auto-GraderPython 엔진, 소스 포함1회 $59, O/X·동그라미 단어·순서, 선생님 표시 유지
    -

    자주 묻는 질문

    -
    어떤 표시를 읽나요?

    단일·복수 답안 버블, 동그라미 친 단어, O/X 상자, 순서 버블을 읽어요.

    선생님이 이미 넣은 성적을 덮어쓰나요?

    아니요. 확실하게 읽힌 답만 성적에 채우고, 선생님이 넣은 표시는 그대로 둬요. 선생님 표시와 다른 항목은 다시 확인할 목록에 올려요.

    오픈 소스 대안도 있나요?

    네. 예를 들어 버블 답안지용 OMRChecker(MIT)가 있어요. 이 엔진은 O/X, 동그라미 단어, 순서 문항과 확실하게 읽힌 답만 채우는 규칙을 더해요.

    제 스캐너에서도 되나요?

    스캐너 종류와 상관없이 스캔한 이미지에서 동작해요. QR Cropper와 함께 쓰거나, 둘 다 Scan-to-Grade Bundle로 받을 수 있어요.

    구독인가요?

    아니요. 1회 구매이고 전체 소스가 포함돼요.

    AGPL 조건이 없는 상용 OMR 라이브러리가 있나요?

    네. 이 엔진은 207studio 라이선스로 전체 소스와 함께 나와요. MarkShark는 AGPL이고 OpenMCR은 GPL-3예요.

    어떤 등급을 사야 하나요?

    Personal은 한 사람용이고, 직접 만든 유료·무료 앱에 컴파일하거나 번들한 코드로 넣을 수 있어요. Commercial은 회사나 조직에서 여러 명이 쓸 때이고 우선 이메일 지원이 붙어요. 두 등급 모두 소스 자체를 공유할 수는 없고, 전체 소스는 포함돼요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $59 · Commercial $249

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/padsway-core-sdk/index.html b/ko/products/padsway-core-sdk/index.html index 35ad9bf..a46d817 100644 --- a/ko/products/padsway-core-sdk/index.html +++ b/ko/products/padsway-core-sdk/index.html @@ -4,27 +4,14 @@ - - -PairyCore SDK | 207 Studio - +207 Studio + - - - - - - - - - - - - - + + + -

    PairyCore SDK | 207 Studio

    -

    PairyCore SDK로 이동

    +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/pairy-core-sdk/index.html b/ko/products/pairy-core-sdk/index.html index 4553739..a46d817 100644 --- a/ko/products/pairy-core-sdk/index.html +++ b/ko/products/pairy-core-sdk/index.html @@ -3,62 +3,15 @@ - - - - -PairyCore SDK: Mac 화면을 iPad로 스트리밍 (Swift) | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    PairyCore SDK: Mac 화면을 iPad로 스트리밍 (Swift)

    -

    Mac 화면을 iPad에 띄우고, 터치와 키보드, 클립보드, 받아쓰기를 다시 Mac으로 보내요. 실제로 동작하는 Mac + iPad 원격 앱의 스트리밍 엔진을 Swift 패키지로 묶었어요.

    - -PairyCore SDK: Mac 화면을 iPad로 스트리밍 (Swift) 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Commercial $129

    -

    1회 구매.

    -

    회사나 조직(여러 명)용이고, 직접 만든 앱에 컴파일된 코드로 넣을 수 있어요. 우선 이메일 지원이 붙어요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    - -

    무엇인가요

    PairyCore SDK는 macOS 13+와 iPadOS 17+에서 Mac 화면을 iPad로 보내는 순수 Swift 패키지예요. ScreenCaptureKit으로 화면을 잡고, VideoToolbox H.264/HEVC로 60 또는 120 fps(Full HD)로 인코딩·디코딩해요. iPad가 보내는 확인 응답에 맞춰 프레임을 조절해 지연을 낮게 유지하고, 포인터·키·텍스트·Apple Pencil·클립보드를 TLS 1.2와 페어링별 키로 되돌려 보내요. 전체 소스와 550개 이상의 테스트가 포함돼요.

    -

    빠르게 시작하기

    swift build
    -swift run padsway self-test
    -swift test
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    RealVNC SDK상용 VNC SDK가격은 견적, £1 한 달 평가판
    Splashtop OEM SDK상용 OEM·자체 브랜드가격은 견적
    RoyalVNC무료 오픈 소스 Swift VNC/RFB SDKVNC 프로토콜, 하드웨어 HEVC 파이프라인 없음
    PairyCore SDKSwift 패키지, 소스 포함1회 $129, ScreenCaptureKit + VideoToolbox, 수신 측 응답에 맞춘 프레임, TLS 페어링
    -

    자주 묻는 질문

    -
    Swift로 Mac 화면을 iPad에 스트리밍하려면 어떻게 하나요?

    두 앱 모두에 PairyCore를 넣어요. Mac에서는 Pairing을 만들고 HostSession을 시작해요. iPad에서는 페어링 정보를 불러와 ClientSession을 연결한 뒤, 넘겨주는 CVPixelBuffer를 그리면 돼요. 연결 방법은 INTEGRATION.md에 있고, 그 코드는 테스트에서 컴파일 검증을 거쳐요.

    Mac이나 네트워크, 권한 없이도 시험해 볼 수 있나요?

    네. `swift run padsway self-test`가 120개 프레임을 인코딩해서 루프백 TLS로 보내고 약 2초 안에 디코딩해요. ClientSession.demo()는 시뮬레이션한 Mac을 프로세스 안에서 스트리밍해서 온보딩과 App Review에 쓸 수 있어요.

    Mac 쪽을 Mac App Store에 올릴 수 있나요?

    아니요. 호스트가 손쉬운 사용(Accessibility) API로 입력을 주입하기 때문에 샌드박스에서 돌 수 없어요. 서명하고 공증한 앱으로 배포해야 해요. iPad 쪽은 이런 제한이 없어요.

    다른 네트워크에서도 되나요?

    기본으로는 같은 로컬 네트워크에서 동작해요. 포트 포워딩 없이 쓰고 싶을 때를 위해 오픈 소스 tsnet 라이브러리로 만든 선택형 브리지가 소스로 들어 있어요.

    프레임 레이트와 해상도는 어떻게 되나요?

    Full HD(1920×1080)예요. ProMotion iPad에서는 120 fps를 요청하고 다른 기기에서는 60 fps를 요청해요. 처리량을 보장하는 숫자는 아니에요. 네트워크 지연이 갑자기 커지면 느린 iPad는 몇 초 동안 약 11 fps까지 떨어질 수 있어요.

    구독인가요?

    아니요. 1회 구매이고 전체 소스가 포함돼요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Commercial $129

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/pptx-player-sdk/index.html b/ko/products/pptx-player-sdk/index.html index 3df2873..a46d817 100644 --- a/ko/products/pptx-player-sdk/index.html +++ b/ko/products/pptx-player-sdk/index.html @@ -3,62 +3,15 @@ - - - - -PPTX Player SDK: 브라우저에서 PowerPoint 재생 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    PPTX Player SDK: 브라우저에서 PowerPoint 재생

    -

    PowerPoint를 브라우저에서 애니메이션까지 재생해요. .pptx를 HTML/SVG로 바꾸는 클라이언트 사이드 엔진이고, 오프라인에서도 돌아가요.

    - -PPTX Player SDK: 브라우저에서 PowerPoint 재생 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Developer $14 · Commercial $129

    -

    1회 구매.

    - - -

    무엇인가요

    PPTX Player SDK는 PowerPoint .pptx 파일을 HTML과 SVG로 렌더링하고, 클릭할 때마다 다음 단계로 넘어가며 재생하는 클라이언트 사이드 JavaScript 엔진이에요. 입장·퇴장·강조 효과, 모프 전환, 포함된 미디어까지 다뤄요. 최신 브라우저나 WebView에서 오프라인으로 돌아가고, 서버 변환도 Office 설치도 필요 없어요. 전체 소스가 포함돼요.

    -

    빠르게 시작하기

    python3 -m http.server 8000
    -open http://localhost:8000/demo.html
    -→ 또는 Space로 슬라이드를 넘겨요
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    @web-ppt/viewer-core무료 npm 패키지클릭 애니메이션과 41개 전환
    pptx-viewer (ChristopherVR)무료 오픈 소스애니메이션 프리셋 39개, Morph 포함 전환 57개
    SlideKiln호스팅 변환 API무료 월 2회, Pro 월 $29
    Aspose.Slides서버 사이드 SDK$999부터
    PPTX Player SDK클라이언트 사이드 엔진, 소스 포함1회 $14, 브라우저나 WKWebView에서 오프라인 재생
    -

    자주 묻는 질문

    -
    PowerPoint 파일을 브라우저에서 애니메이션까지 재생할 수 있나요?

    네. 엔진이 슬라이드마다 HTML/SVG로 렌더링하고, 클릭 순서대로 입장·퇴장·강조 효과, 모프 전환, 미디어를 순서대로 재생해요.

    서버나 Microsoft Office가 필요한가요?

    아니요. 모든 처리가 클라이언트에서 돌아가요. 플레이어 폴더를 아무 정적 웹 서버에 올리고 브라우저에서 열면 돼요.

    오프라인이나 네이티브 앱 안에서도 되나요?

    네. 오프라인으로 돌아가고, iOS·iPadOS·macOS에서는 같은 폴더를 WKWebView에 올릴 수 있어요. Android와 Electron WebView도 같은 방식으로 동작해요.

    슬라이드 XML이 깨져 있으면 어떻게 되나요?

    엔진이 잘못 들어간 & 문자를 고쳐서 슬라이드를 정상으로 재생해요. 그래도 XML이 깨져 있으면 할 수 있는 만큼 렌더링하고 오류를 알려요. 슬라이드를 조용히 빠뜨리지는 않아요.

    구독인가요?

    아니요. 1회 구매이고 전체 소스가 포함돼요.

    JavaScript용 무료 PPTX 뷰어도 있나요?

    네. 예를 들어 @web-ppt/viewer-core와 pptx-viewer가 있어요. 서버 없이 전체 소스가 있는 클라이언트 사이드 엔진 하나를 쓰고 싶고, 직접 포크를 관리하는 대신 한 번 결제하고 싶다면 이 SDK를 고르면 돼요.

    어떤 등급을 사야 하나요?

    Developer는 개발자 한 명용이고, 직접 만든 유료·무료 앱에 플레이어를 번들 코드로 넣을 수 있어요. Commercial은 회사나 조직에서 여러 명이 쓸 때이고 우선 이메일 지원이 붙어요. 두 등급 모두 소스 자체를 공유할 수는 없고, 전체 소스는 포함돼요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Developer $14 · Commercial $129

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/qr-cropper/index.html b/ko/products/qr-cropper/index.html index d711235..a46d817 100644 --- a/ko/products/qr-cropper/index.html +++ b/ko/products/qr-cropper/index.html @@ -3,62 +3,15 @@ - - - - -QR Answer-Sheet Cropper: 답안 칸마다 잘라내기 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    QR Answer-Sheet Cropper: 답안 칸마다 잘라내기

    -

    답안지마다 QR을 인쇄하고, 뒤섞인 묶음을 스캔하면 답안 칸마다 반듯하게 잘린 이미지가 나와요.

    - -QR Answer-Sheet Cropper: 답안 칸마다 잘라내기 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $39 · Commercial $149

    -

    1회 구매.

    - - -

    무엇인가요

    QR Answer-Sheet Cropper는 스캔한 시험지를 다루는 Python + OpenCV 도구예요. 답안지마다 작은 QR을 인쇄하고, 스캐너에서 나온 뒤섞인 묶음을 넣어요. 페이지마다 QR로 찾아내고 앞면과 뒷면을 짝지어요. 회전과 기울기를 바로잡고 답안 칸마다 약 200 dpi의 반듯한 이미지로 잘라요. 오프라인으로 돌아가요.

    -

    빠르게 시작하기

    pip install -r requirements.txt
    -python demo.py
    -open out/
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    Stack Overflow / OpenCV 포럼 코드무료 코드 조각QR 기준 원근 보정, 나머지는 직접 작성
    OMR 프로젝트 (rbaron/omr, OMR-Grader)무료 오픈 소스버블 채점
    Dynamsoft Document NormalizerSDK연 $1,249부터
    Aspose.OMRSDK$599부터
    QR Answer-Sheet CropperPython 패키지, 소스 포함1회 $39, 묶음 정렬·앞뒤 짝짓기·칸 자르기
    -

    자주 묻는 질문

    -
    QR 코드로 스캔한 시험지의 답안 영역을 어떻게 자르나요?

    답안지마다 QR 값을 인쇄하고, 묶음을 스캔한 뒤 크로퍼를 실행해요. 페이지 모서리의 QR을 읽고 답안 표를 밀리미터 격자에 맞춰 보정한 다음 칸마다 잘라요. Python에서는 데모를 실행하면 페이지마다 QR로 찾아내고, 템플릿에 맞춰 기울기를 바로잡고, 답안 칸마다 이미지 하나로 잘라요.

    뒤섞인 스캔 묶음을 학생별로 정렬할 수 있나요?

    네. QR 코드로 학생마다 앞면과 뒷면을 짝지어요. 다른 시험의 페이지는 '다른 시험 페이지(stray)'로 표시해요.

    회전이나 기울기가 있어도 되나요?

    네. 기울기와 90/180/270도 회전을 처리해요.

    스캔 해상도는 얼마나 필요하나요?

    150 dpi 이상이면 돼요. 데모는 200 dpi를 써요.

    실행하려면 무엇이 필요한가요?

    Windows, macOS, Linux에서 Python 3.10 이상, opencv-python-headless, numpy가 필요해요. 1회 구매이고 전체 소스가 포함돼요.

    어떤 등급을 사야 하나요?

    Personal은 한 사람용이고, 직접 만든 유료·무료 앱에 컴파일하거나 번들한 코드로 넣을 수 있어요. Commercial은 회사나 조직에서 여러 명이 쓸 때이고 우선 이메일 지원이 붙어요. 두 등급 모두 소스 자체를 공유할 수는 없고, 전체 소스는 포함돼요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $39 · Commercial $149

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/scan-to-grade-bundle/index.html b/ko/products/scan-to-grade-bundle/index.html index b8cc2b0..a46d817 100644 --- a/ko/products/scan-to-grade-bundle/index.html +++ b/ko/products/scan-to-grade-bundle/index.html @@ -3,62 +3,15 @@ - - - - -Scan-to-Grade: 스캔한 시험지를 Excel로 (Python) | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Scan-to-Grade: 스캔한 시험지를 Excel로 (Python)

    -

    QR Cropper와 OMR Auto-Grader를 함께 묶었어요. 스캔한 답안지 묶음을 넣으면 Excel 성적표가 나와요.

    -

    QR Answer-Sheet Cropper($39)와 OMR Auto-Grader($59)가 들어 있어요. 따로 사면 $98이에요. QR Answer-Sheet Cropper · OMR Auto-Grader

    -Scan-to-Grade: 스캔한 시험지를 Excel로 (Python) 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $79 · Commercial $299

    -

    1회 구매.

    - - -

    무엇인가요

    Scan-to-Grade는 스캔한 답안지 묶음을 Excel 성적표로 바꾸는 Python 패키지예요. 각 페이지의 QR을 읽고 앞면과 뒷면을 짝지어요. 기울기를 바로잡고 답안 칸마다 잘라낸 뒤, 버블과 복수 답안, O/X 문항, 순서 버블을 채점해요. 점수와 문항 분석, 다시 확인할 목록을 만들고, 오프라인으로 돌아가요.

    -

    빠르게 시작하기

    pip install -r requirements.txt
    -python demo.py
    -open out/
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    Remark Office OMR데스크톱 OMR 소프트웨어$1,195부터 $1,295까지
    GradientOMR 채점 서비스연 $149.99
    OMRChecker무료 오픈 소스 (MIT)버블 채점, 스캔에서 Excel까지는 직접 만들어야 함
    EUI OMR Engine무료 오픈 소스OMR과 Excel 명단 쓰기
    Scan-to-Grade BundlePython 패키지, 소스 포함1회 $79, QR 페이지 정렬·자르기·채점·Excel 내보내기를 한 번에
    -

    자주 묻는 질문

    -
    스캔한 종이 시험지를 자동으로 Excel 성적표로 바꿀 수 있나요?

    네. 스캐너에서 나온 묶음을 폴더에 넣고 실행하면 돼요. 점수와 문항 분석, 다시 확인할 목록이 든 gradebook.xlsx와 results.json, recheck.csv를 받아요.

    학생 데이터가 컴퓨터 밖으로 나가나요?

    아니요. Windows, macOS, Linux에서 오프라인으로 돌아가요. 어디에도 업로드하지 않아요.

    페이지 순서나 회전이 뒤섞여도 되나요?

    네. 페이지마다 QR이 있어서 뒤섞인 묶음, 뒷면, 90/180/270도 회전을 자동으로 정리해요.

    손글씨나 서술형도 채점하나요?

    아니요. 단답형, 서술형, 손글씨는 선생님이 맡아요. 잘라낸 답안 칸 이미지를 보고 확인할 수 있어요.

    실행하려면 무엇이 필요한가요?

    Python 3.10 이상, opencv-python-headless, numpy, openpyxl이 필요해요. 1회 구매이고 전체 소스가 포함돼요.

    어떤 등급을 사야 하나요?

    Personal은 한 사람용이고, 직접 만든 유료·무료 앱에 컴파일하거나 번들한 코드로 넣을 수 있어요. Commercial은 회사나 조직에서 여러 명이 쓸 때이고 우선 이메일 지원이 붙어요. 두 등급 모두 소스 자체를 공유할 수는 없고, 전체 소스는 포함돼요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $79 · Commercial $299

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/send-approval-gate-mcp/index.html b/ko/products/send-approval-gate-mcp/index.html index 78a2abb..a46d817 100644 --- a/ko/products/send-approval-gate-mcp/index.html +++ b/ko/products/send-approval-gate-mcp/index.html @@ -3,62 +3,15 @@ - - - - -Send Approval Gate MCP: AI 에이전트의 발송을 한 번만 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Send Approval Gate MCP: AI 에이전트의 발송을 한 번만

    -

    AI 에이전트가 계획을 세우고, 해시로 묶인 승인을 정확히 보여 주고, 한 번만 보내고 확인하게 만드는 MCP 서버예요. 모든 단계를 기록해요.

    - -Send Approval Gate MCP: AI 에이전트의 발송을 한 번만 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Developer $29 · Commercial $99

    -

    1회 구매.

    - - -

    무엇인가요

    Send Approval Gate MCP는 Node.js 22+에서 도는 MCP 서버(stdio)예요. MCP 도구는 status, account, contacts, plan-send, send, verify-send 여섯 개예요. 승인에는 계획 ID, 계정, 수신자, 메시지 SHA-256, 백엔드, 환경 해시가 묶여서, 하나라도 바뀌면 새 승인이 필요해요. 중복 발송은 막고, 결과가 불확실하면 result_unknown으로 남겨서 자동으로 다시 시도하지 않아요. 모든 단계는 크래시 복구가 되는 로컬 원장에 기록돼요. 직접 쓰는 채널은 작은 백엔드 플러그인 계약으로 붙이고, mock(테스트용) 백엔드와 예제 플러그인이 들어 있어요. 테스트 47개, npm 의존성 없음. 협조적인 에이전트가 보내는 메시지를 정확히 한 번만 보내고, 그 과정을 감사할 수 있게 해 줘요. 신뢰하지 않는 에이전트를 막는 보안 경계는 아니에요.

    -

    빠르게 시작하기

    node --test test/*.test.mjs
    -node src/cli.mjs --backend mock mcp
    -MCP 클라이언트 설정에 추가해요
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    직접 쓴 확인 프롬프트에이전트에게 확인 요청해시 묶임 없음, 원장 없음, 재시도하면 두 번 발송될 수 있음
    일반 HITL 프레임워크에이전트 프레임워크 안의 승인 단계한 프레임워크에 묶임, MCP 서버가 아님
    Send Approval Gate MCPMCP 서버, 소스 포함1회 $29, 해시로 묶인 승인, 한 번 발송, result_unknown, 원장
    -

    자주 묻는 질문

    -
    AI 에이전트가 메시지를 두 번 보내지 않게 하려면 어떻게 하나요?

    발송은 plan-send와 send를 거치게 해요. 계획은 한 번만 보낼 수 있고, 타임아웃이나 어댑터 실패는 result_unknown으로 기록해서 자동으로 다시 시도하지 않아요.

    Slack, 이메일, 메신저 백엔드가 들어 있나요?

    아니요. 직접 쓰는 채널에 맞춰 플러그인 함수 status, account, contacts, send, verifySend가 있는 작은 플러그인을 만들어요. mock(테스트용) 백엔드와 예제 플러그인이 들어 있어요.

    보안 경계인가요?

    아니요. 승인은 send에 넘기는 값이라, 모든 도구를 호출할 수 있는 에이전트는 승인도 만들 수 있어요. 사람만 승인하게 하려면 에이전트가 닿을 수 없는 채널에서 승인을 만들고 send를 호출해요.

    무엇이 필요한가요?

    Node.js 22 이상과 stdio를 쓰는 MCP 클라이언트가 필요해요. npm 의존성은 없어요.

    구독인가요?

    아니요. 1회 구매이고 전체 소스가 포함돼요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Developer $29 · Commercial $99

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/shapefit/index.html b/ko/products/shapefit/index.html index 138e631..a46d817 100644 --- a/ko/products/shapefit/index.html +++ b/ko/products/shapefit/index.html @@ -3,62 +3,15 @@ - - - - -ShapeFit: Swift와 Rust용 hold-to-shape | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    ShapeFit: Swift와 Rust용 hold-to-shape

    -

    사용자가 펜을 멈추면 획이 깔끔한 선, 원, 사각형, 다각형으로 바뀌어요. 직접 만드는 드로잉 앱에 넣을 수 있어요.

    - -ShapeFit: Swift와 Rust용 hold-to-shape 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $9 · Commercial $29

    -

    1회 구매.

    - - -

    무엇인가요

    ShapeFit은 길게 눌러 도형으로 바꾸는(hold-to-shape) 제스처를 직접 만드는 드로잉 앱에 더해요. 사용자가 획 끝에서 잠시 멈추면 깔끔한 선, 원, 타원, 사각형, 삼각형, 다각형으로 맞춰 주고, 낙서는 그대로 둬요. 순수 기하 계산이라 ML 모델도 없고 의존성도 없어요. Rust 크레이트와 바로 넣는 Swift 파일이 같은 결과를 내요.

    -

    빠르게 시작하기

    cargo test
    -cargo run --bin shapefit-demo
    -open out/shapes.svg
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    Desert Ant Labs ShapesML 모델이 있는 Swift SDKMAU 10만까지 무료
    guessture, $P stroke recognizers무료 Rust 크레이트제스처 인식, 도형 정리는 아님
    ShapeFitRust 크레이트 + Swift 파일, 소스 포함1회 $9, ML 없음, MAU 제한 없음, Rust와 Swift 결과 동일
    -

    자주 묻는 질문

    -
    길게 눌러 도형으로 바꾸는(hold-to-shape) 기능을 내 앱에 어떻게 넣나요?

    펜이 멈출 때 획 좌표를 ShapeFit에 넣어요. 깔끔한 도형을 돌려주고, 낙서면 획을 그대로 둬요.

    PencilKit과도 되나요?

    네. Swift 파일은 PencilKit이나 어떤 캔버스에서도 동작해요.

    어떤 도형을 알아보나요?

    45도 스냅이 되는 선, 원과 타원, 반듯한 사각형, 삼각형, 꼭짓점 6개까지의 다각형을 알아봐요.

    머신러닝을 쓰나요?

    아니요. 의존성도 학습 데이터도 없는 순수 기하 계산이에요. Rust와 Swift 버전이 같은 결과를 내고, 600개 획으로 확인했어요.

    구독인가요?

    아니요. 1회 구매이고 전체 소스가 포함돼요.

    ML도 MAU 제한도 없는 PencilKit 도형 스냅 라이브러리가 있나요?

    네. ShapeFit은 순수 기하 계산이에요. 모델도, 사용자 수 제한도, 의존성도 없고, Rust 크레이트와 Swift 파일이 같은 결과를 내요.

    어떤 등급을 사야 하나요?

    Personal은 한 사람용이고, 직접 만든 유료·무료 앱에 컴파일하거나 번들한 코드로 넣을 수 있어요. Commercial은 회사나 조직에서 여러 명이 쓸 때이고 우선 이메일 지원이 붙어요. 두 등급 모두 소스 자체를 공유할 수는 없고, 전체 소스는 포함돼요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal $9 · Commercial $29

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/ko/products/voice-command-engine/index.html b/ko/products/voice-command-engine/index.html index deac082..a46d817 100644 --- a/ko/products/voice-command-engine/index.html +++ b/ko/products/voice-command-engine/index.html @@ -3,62 +3,15 @@ - - - - -Context-Gated Voice Commands: 오프라인 음성 명령 | 207 Studio - - - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Context-Gated Voice Commands: 오프라인 음성 명령

    -

    오디오 특징으로 지금 상황을 추정하고, 그 상황에 맞는 음성 명령만 통과시키는 Rust 라이브러리와 CLI예요.

    - -Context-Gated Voice Commands: 오프라인 음성 명령 커버 -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal/Research $29 · Commercial $99

    -

    1회 구매.

    - - -

    무엇인가요

    Context-Gated Voice Commands는 Rust 라이브러리와 CLI예요. 수업, 회의, 매장 같은 장면이 지금 어느 단계인지 음향 특징과 단서로 1초마다 추정해요. 그리고 각 음성 명령을 바로 실행할지, 웨이크 워드를 요구할지, 막을지 정해요. 오프라인으로 돌아가고, 교실·회의·매장 프리셋과 시드가 있는 시뮬레이터가 들어 있어요.

    -

    빠르게 시작하기

    cargo build --release
    -./target/release/scene-engine simulate --preset all --sessions 100 --seed 20261018 --out out
    -cat out/report.md
    -

    다른 도구와 비교

    - - - - -
    도구종류메모
    Picovoice Rhino온디바이스 음성 의도 인식비상업 무료(MAU 1), Foundation 연 $6,000
    연구 논문방법만 있음패키지 제품 없음
    취미용 음성 명령 저장소무료 오픈 소스고정 명령 문법, 상황 모델 없음
    Context-Gated Voice CommandsRust 라이브러리 + CLI, 소스 포함1회 $29, 감지한 단계로 명령을 걸러 냄, 오프라인
    -

    자주 묻는 질문

    -
    시끄러운 방에서 음성 명령이 잘못 실행되는 걸 어떻게 줄이나요?

    명령을 상황으로 걸러요. 엔진이 지금 어떤 단계인지 추적하고, 단계마다 명령을 허용하거나 웨이크 워드를 요구하거나 막아요.

    상황은 어떻게 감지하나요?

    단계와 설정한 전이, 음향 프로필(말하는 사람 수, 크기, 겹침, 주 화자 비율, 미디어 재생)에 확률 필터를 적용하고, 선택형 음성·앱 단서를 더해요. 1초에 한 번 갱신해요.

    내 상황을 직접 모델링할 수 있나요?

    네. TOML 파일에 단계, 길이, 전이, 음향 프로필, 명령 규칙을 정의하고 scene-engine validate로 검사한 뒤 시뮬레이션할 수 있어요. 교실, 회의, 매장 프리셋이 기본으로 들어 있어요.

    내 앱에서는 어떻게 쓰나요?

    Rust path 의존성으로 추가하고, 1초에 한 번 관측값을 SceneFilter에 넣고, 명령마다 gate::decide를 호출해요. CLI는 JSONL 관측값을 넣고 단계 스냅샷을 받을 수도 있어요.

    클라우드나 Python이 필요한가요?

    아니요. Rust 1.85 이상에서 로컬로 돌아가고, 계정도 API 키도 Python도 필요 없어요. 1회 구매이고 전체 소스가 포함돼요.

    주변 대화가 음성 명령을 실행하지 않게 하려면 어떻게 하나요?

    명령을 상황으로 걸러요. 엔진이 수업, 회의, 매장 같은 장면의 단계를 음향 특징과 단서로 1초마다 추정하고, 명령을 실행하거나 웨이크 워드를 요구하거나 막아요. Rust 라이브러리와 CLI로 오프라인에서 돌아가요.

    어떤 등급을 사야 하나요?

    Personal/Research는 한 사람용이고, 직접 만든 유료·무료 앱에 엔진을 컴파일된 코드로 넣을 수 있어요. Commercial은 회사나 조직에서 여러 명이 쓸 때이고 우선 이메일 지원이 붙어요. 두 등급 모두 소스 자체를 공유할 수는 없고, 전체 소스는 포함돼요. 1회 구매예요. Commercial은 연 매출 $1M 이하 회사를 대상으로 해요. 그보다 크면 support@207studio.dev로 Enterprise 라이선스를 문의해 주세요.

    결제하면 무엇을 받나요?

    전체 소스와 README, 실행 가능한 데모가 든 zip을 받아요. 결제한 곳에서 결제 직후와 영수증 이메일로 다운로드 링크를 보여 줘요.

    -
    - - -

    기본 결제는 Polar, Paddle로도 카드 또는 PayPal 결제: Personal/Research $29 · Commercial $99

    -

    1회 구매.

    -

    ← 전체 제품

    -
    - +

    이 페이지는 내려갔어요. 207 Studio 홈으로 이동해요. 홈으로 가기

    diff --git a/legal/privacy/index.html b/legal/privacy/index.html index b806164..8f2f18f 100644 --- a/legal/privacy/index.html +++ b/legal/privacy/index.html @@ -33,7 +33,7 @@
    - +

    Privacy Policy

    Effective 19 October 2026.

    @@ -66,5 +66,5 @@

    7. Children

    Our products are sold to adults. We do not knowingly collect data from children.

    Terms of Service · Refund Policy · Home

    -
    +
    diff --git a/legal/refunds/index.html b/legal/refunds/index.html index bfc3b49..9f5bdac 100644 --- a/legal/refunds/index.html +++ b/legal/refunds/index.html @@ -31,7 +31,7 @@
    - +

    Refund Policy

    Effective 19 October 2026.

    @@ -51,5 +51,5 @@

    5. Changing your mind

    Digital files are delivered immediately, so a change of mind after a successful download is handled under the store's policy, not ours. If you are unsure before buying, write to us with your question first; we answer setup questions before the sale.

    Terms of Service · Privacy Policy · Home

    -
    +
    diff --git a/legal/terms/index.html b/legal/terms/index.html index aa32296..381c79e 100644 --- a/legal/terms/index.html +++ b/legal/terms/index.html @@ -31,7 +31,7 @@
    - +

    Terms of Service

    Effective 19 October 2026. Seller: 207스튜디오 (207 Studio), Republic of Korea.

    @@ -63,5 +63,5 @@

    9. Changes

    We may update these terms for future purchases. The version that applied to your order is the one published on the day you bought.

    Privacy Policy · Refund Policy · Home

    -
    +
    diff --git a/llms-full.txt b/llms-full.txt index eb04e6a..190dab6 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,159 +1,31 @@ -# 207 Studio: full product reference +# 207 Studio: full reference -> https://207studio.dev · support@207studio.dev · South Korea. One-time purchase, full source. Commercial tiers cover companies with annual revenue up to $1M; above that, email support@207studio.dev for an Enterprise license. +> https://207studio.dev · support@207studio.dev · South Korea. Founder: Jang Yuil (장유일) -## PPTX Player SDK – Play PowerPoint in the Browser - -URL: https://207studio.dev/products/pptx-player-sdk/ - -Play PowerPoint in the browser with every animation: a client-side PPTX to HTML/SVG engine that works offline. - -Client-side JS engine that plays .pptx click-by-click: build animations, morph, media, offline. No server, no Office. One-time $14, source included. - -Tiers (USD, one-time): -- Developer: $14 - buy: https://buy.polar.sh/polar_cl_75Don0VoNVcrgJ7mx9b32yZq5v4ZydBu37uwN1getXd -- Commercial: $129 - buy: https://buy.polar.sh/polar_cl_6i0GKBLIeSNUMjqLDt8U9dAVfJxnGIrx7j5qf36gWcX - -Developer is for one developer, and that includes shipping the player inside your own paid or free apps as bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. - -## PairyCore SDK: Mac-to-iPad Screen Streaming - -URL: https://207studio.dev/products/pairy-core-sdk/ - -Put a live Mac screen on an iPad and send touch, keyboard, clipboard and dictation back: the streaming engine from a working Mac + iPad remote app, as a Swift package. - -Swift package that streams a Mac screen to an iPad with hardware H.264/HEVC, 60/120 fps and touch, keyboard and clipboard back. Full source. One-time purchase. - -Tiers (USD, one-time): -- Commercial: $129 - buy: https://buy.polar.sh/polar_cl_X4vNDyrdEHsK4LYKEg9WZWqtrbe6SzxzHU8zu0lQfSH - -For a company or organisation (more than one person), including in its apps as compiled code, with priority email support. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license. - -## Scan-to-Grade: Scanned Exams to Excel (Python) - -URL: https://207studio.dev/products/scan-to-grade-bundle/ - -QR Cropper and OMR Auto-Grader together: a scanned batch in, an Excel gradebook out. - -Cropper + OMR grader together: drop in a scanner batch, get an Excel gradebook. Runs offline; student data never leaves your PC. One-time, source. - -Tiers (USD, one-time): -- Personal: $79 - buy: https://buy.polar.sh/polar_cl_8xf9e2PzkppWPIh7ZQ339nUMWpKB2jNuTtbqm3pruPj -- Commercial: $299 - buy: https://buy.polar.sh/polar_cl_DrRGNlKtA1G62gZFWa1OcfYSAXcrPIQmmi7Qb1492lC - -Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. - -Includes: -- text -- links - -## OMR Auto-Grader – Bubbles, O/X, Circled Answers - -URL: https://207studio.dev/products/omr-auto-grader/ - -Reads bubbles, circled words, O/X boxes and ordering marks on scanned sheets, and fills in only the grades it is sure of. - -Python OMR that grades bubbles, multi-marks, O/X and circled answers, fills only confident grades, never overwrites teacher marks. One-time purchase. - -Tiers (USD, one-time): -- Personal: $59 - buy: https://buy.polar.sh/polar_cl_xJBFocaAKSaFNLgPJ2VNceLFqkmxwm4coi0el0BvhI6 -- Commercial: $249 - buy: https://buy.polar.sh/polar_cl_pqx1PzwSBoayvSaeHpmuLc64XiqSolIxeC1re3Q13oo - -Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. - -## QR Answer-Sheet Cropper – Crop Every Answer Box - -URL: https://207studio.dev/products/qr-cropper/ - -Print a QR on each sheet, scan a mixed batch, and get every answer cell back as a clean, aligned image. - -Print a QR on each page, scan a mixed batch, get every answer cell as an aligned image. Python + OpenCV, runnable demo, one-time purchase. - -Tiers (USD, one-time): -- Personal: $39 - buy: https://buy.polar.sh/polar_cl_QnpaugxcXgYeD3NfSmdOoQIZ8s5NxWnsXHF250GoQDD -- Commercial: $149 - buy: https://buy.polar.sh/polar_cl_LHfw7BoxPMPXDMx5scbvqxyWA0f2QWrbvO9CL0f3euT - -Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. - -## Context-Gated Voice Commands – Offline - -URL: https://207studio.dev/products/voice-command-engine/ - -A Rust library and CLI that infers the current situation from audio features and lets through only the voice commands that fit it. - -Rust engine that tracks the current situation second by second and runs only the voice commands that fit it, ignoring background talk. Offline. - -Tiers (USD, one-time): -- Personal/Research: $29 - buy: https://buy.polar.sh/polar_cl_wTaaxiMXwBpLPJBlzBKZ4xGAa4JTMz9kzp24r0g8L8Y -- Commercial: $99 - buy: https://buy.polar.sh/polar_cl_mCFCRFkwWmVTk9k5xz4YeJUkURL9oVwEzz8nv3I7uAZ - -Personal/Research is for one person, and that includes shipping the engine inside your own paid or free apps as compiled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. - -## ShapeFit – Hold-to-Shape for Swift & Rust - -URL: https://207studio.dev/products/shapefit/ - -Turn a hand-drawn stroke into a clean line, circle, rectangle or polygon when the user pauses, for your own drawing app. - -Pause on a stroke and it snaps to a clean line, circle, rectangle or polygon. No ML, no MAU limits, zero dependencies. PencilKit-ready. $9, source. - -Tiers (USD, one-time): -- Personal: $9 - buy: https://buy.polar.sh/polar_cl_tZQVB8rHo2KdpUwZrUmXr80Sb6RO9xRNwV3qZ2jd3xV -- Commercial: $29 - buy: https://buy.polar.sh/polar_cl_sgn4wIILsccvCLPOfmephnAIJCKoh7Q8WHWrp2iJFdE - -Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. - -## Exam + OMR Sheet Builder – Paper, Sheet & Key - -URL: https://207studio.dev/products/exam-sheet-builder/ - -Write an exam once and print a matching exam paper, OMR answer sheet, answer key, explanation sheet and cover page. - -Write an exam once; print the paper, OMR sheet, answer key, explanations and cover, always matching. Offline single file + MCP server for AI. $12. - -Tiers (USD, one-time): -- Personal: $12 - buy: https://buy.polar.sh/polar_cl_N7RdBS3uUXglH4Kk6ei4gjn42J2ehQxT18EvN372jsJ -- Commercial: $35 - buy: https://buy.polar.sh/polar_cl_tctZA7z5EjURT3yPhV4ozdQQxZwfjJLyn8MVX3Ab9BE - -Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. - -## Offline Classroom Tools – Seating, PACER Timer - -URL: https://207studio.dev/products/classroom-html-tools/ - -Four single-file classroom tools: a seating chart with attendance, a morning board, a fitness test recorder and Wi-Fi QR cards. - -Four single-file HTML tools: seating + attendance board, weekly attendance to Excel, PACER beep test recorder, Wi-Fi QR cards. No account. $9. - -Tiers (USD, one-time): -- Teacher: $9 - buy: https://buy.polar.sh/polar_cl_QTRPbU8ny7asrhpuYWkaZ1I3wGILT2qXKlIUG1ZQ3Ue -- School: $19 - buy: https://buy.polar.sh/polar_cl_6CQvHSVi6j8a5Z5bNYTC1rEoeprhab1xfob2J06on67 - -Teacher is for one teacher, in your own classes or inside your own paid or free products. School covers every teacher and staff member in one school and adds priority email support. Both include all four tools. One-time purchase. - -## Send Approval Gate MCP: Plan, Approve, Send, Verify - -URL: https://207studio.dev/products/send-approval-gate-mcp/ +## Pairy: Connecting Devices (iOS/iPadOS app) -An MCP server that makes an AI agent plan, present an exact hash-bound approval, send once and verify, and records every step. +https://apps.apple.com/app/id6815584225 - remote desktop for your tablet. Paid app, one-time purchase ($4.99 in the US). +Page: https://207studio.dev/pairy/ (how it works, screenshots, the Mac companion download) -Node.js 22+ MCP server (stdio) with six tools: status, account, contacts, plan-send, send and verify-send. The approval binds plan ID, account, recipient, message SHA-256, backend and environment hash. Duplicate sends are blocked, an uncertain result becomes result_unknown and is never retried, and every step goes to a local ledger with crash recovery. 47 tests, no npm dependencies. +## Pairy for Mac 1.4.2 (build 142) -Tiers (USD, one-time): -- Developer: $29 - buy: https://buy.polar.sh/polar_cl_pisramo6U68y8MnVzhsf8GEGaI9YAIbFTQsHo34ITnm -- Commercial: $99 - buy: https://buy.polar.sh/polar_cl_LlYHKLJekKIlUW6LAdcbOkqzpo9zA6FoBFqgz43kFXg +Download: https://github.com/207studio/207studio.github.io/releases/download/pairy-mac-1.4.2-142/Pairy-mac-1.4.2-142-notarized.zip -Developer: one developer, own projects only, no company use. Commercial: one organisation and its staff, including in its products as compiled or bundled code, with priority email support. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license. +Apple Silicon only, macOS 15 or later. Notarized with Developer ID. -## Pairy: Connecting Devices (iOS/iPadOS app) +File: Pairy-mac-1.4.2-142-notarized.zip (22075699 bytes) +SHA-256: 38584e50e1963fd45369ebe496bce3324515651f1f81946a5248dcca23fc1ded -https://apps.apple.com/app/id6815584225 - remote desktop for your tablet. Paid app, one-time purchase ($4.99 in the US). +Double-click the zip to unzip it, then move PairyMac.app into Applications. -## pptx-mcp (free) +## In development -https://github.com/207studio/pptx-mcp - MCP server for opening and presenting .pptx files from an AI agent. +- [Chalkieboard](https://chalkieboard.207studio.dev/) (초키보드): an iPad lesson board app for elementary school teachers. Board writing with subject tools, PPTX and HWP lesson files opened in the app, and AirPlay to the classroom display. No personal data is collected during class. In beta on TestFlight. Support: https://chalkieboard.207studio.dev/support/ · Tester guide: https://chalkieboard.207studio.dev/beta/ +- CheckyBoard: a Windows app for checking paper exams one question at a time. The teacher marks each answer, and the app makes answer sheets, splits scans into per-question boxes, and produces scores, item analysis and report cards. +- [StoneWars](https://207studio.dev/stonewars/): five in a row on a 15×15 board in 3D for iPhone, iPad and Mac, with eight stone classes and seven worlds. Preparing for App Store release. +- Ttoryeot (또렷): an HWP/HWPX document engine written in Rust. Opens Korean 한글 documents and renders them to PDF or images without 한글 installed. View only. One engine for Mac, iOS, Windows and the web. Not released yet. ## Contact diff --git a/llms.txt b/llms.txt index cc60686..c812350 100644 --- a/llms.txt +++ b/llms.txt @@ -1,29 +1,20 @@ # 207 Studio -> Developer kits and classroom tools from 207 Studio (207studio.dev), South Korea. One-time purchase, full source. +> Apps from 207 Studio (207studio.dev), an independent software studio in South Korea. Founder: Jang Yuil (장유일) -## Products - -- [PPTX Player SDK – Play PowerPoint in the Browser](https://207studio.dev/products/pptx-player-sdk/): Play PowerPoint in the browser with every animation: a client-side PPTX to HTML/SVG engine that works offline. Prices: Developer $14 / Commercial $129. -- [PairyCore SDK: Mac-to-iPad Screen Streaming](https://207studio.dev/products/pairy-core-sdk/): Put a live Mac screen on an iPad and send touch, keyboard, clipboard and dictation back: the streaming engine from a working Mac + iPad remote app, as a Swift package. Prices: Commercial $129. -- [Scan-to-Grade: Scanned Exams to Excel (Python)](https://207studio.dev/products/scan-to-grade-bundle/): QR Cropper and OMR Auto-Grader together: a scanned batch in, an Excel gradebook out. Prices: Personal $79 / Commercial $299. -- [OMR Auto-Grader – Bubbles, O/X, Circled Answers](https://207studio.dev/products/omr-auto-grader/): Reads bubbles, circled words, O/X boxes and ordering marks on scanned sheets, and fills in only the grades it is sure of. Prices: Personal $59 / Commercial $249. -- [QR Answer-Sheet Cropper – Crop Every Answer Box](https://207studio.dev/products/qr-cropper/): Print a QR on each sheet, scan a mixed batch, and get every answer cell back as a clean, aligned image. Prices: Personal $39 / Commercial $149. -- [Context-Gated Voice Commands – Offline](https://207studio.dev/products/voice-command-engine/): A Rust library and CLI that infers the current situation from audio features and lets through only the voice commands that fit it. Prices: Personal/Research $29 / Commercial $99. -- [ShapeFit – Hold-to-Shape for Swift & Rust](https://207studio.dev/products/shapefit/): Turn a hand-drawn stroke into a clean line, circle, rectangle or polygon when the user pauses, for your own drawing app. Prices: Personal $9 / Commercial $29. -- [Exam + OMR Sheet Builder – Paper, Sheet & Key](https://207studio.dev/products/exam-sheet-builder/): Write an exam once and print a matching exam paper, OMR answer sheet, answer key, explanation sheet and cover page. Prices: Personal $12 / Commercial $35. -- [Offline Classroom Tools – Seating, PACER Timer](https://207studio.dev/products/classroom-html-tools/): Four single-file classroom tools: a seating chart with attendance, a morning board, a fitness test recorder and Wi-Fi QR cards. Prices: Teacher $9 / School $19. -- [Send Approval Gate MCP: Plan, Approve, Send, Verify](https://207studio.dev/products/send-approval-gate-mcp/): An MCP server that makes an AI agent plan, present an exact hash-bound approval, send once and verify, and records every step. Prices: Developer $29 / Commercial $99. - ## Apps -- [Pairy: Connecting Devices](https://apps.apple.com/app/id6815584225): Remote desktop for your tablet. Shows your computer's screen on your tablet and lets you control it with touch, trackpad and keyboard. Paid app, one-time purchase ($4.99 in the US). +- [Pairy: Connecting Devices](https://apps.apple.com/app/id6815584225): Remote desktop for your tablet. Shows your computer's screen on your tablet and lets you control it with touch, trackpad and keyboard. Paid app, one-time purchase ($4.99 in the US). How it works and the Mac companion: https://207studio.dev/pairy/ +- [Pairy for Mac 1.4.2 (build 142)](https://github.com/207studio/207studio.github.io/releases/download/pairy-mac-1.4.2-142/Pairy-mac-1.4.2-142-notarized.zip): direct download. Apple Silicon only, macOS 15 or later. Notarized with Developer ID. Double-click the zip to unzip it, then move PairyMac.app into Applications. -## Licensing +## In development -- Commercial licenses cover companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license. All purchases are one-time; no subscriptions. +- [Chalkieboard](https://chalkieboard.207studio.dev/) (초키보드): an iPad lesson board app for elementary school teachers. Board writing with subject tools, PPTX and HWP lesson files opened in the app, and AirPlay to the classroom display. No personal data is collected during class. In beta on TestFlight. +- CheckyBoard: a Windows app for checking paper exams one question at a time. The teacher marks each answer, and the app makes answer sheets, splits scans into per-question boxes, and produces scores, item analysis and report cards. +- [StoneWars](https://207studio.dev/stonewars/): five in a row on a 15×15 board in 3D for iPhone, iPad and Mac, with eight stone classes and seven worlds. Preparing for App Store release. +- Ttoryeot (또렷): an HWP/HWPX document engine written in Rust. Opens Korean 한글 documents and renders them to PDF or images without 한글 installed. View only. One engine for Mac, iOS, Windows and the web. Not released yet. ## Writing @@ -31,41 +22,10 @@ Founder: Jang Yuil (장유일) ## 한국어 -- [207스튜디오 홈](https://207studio.dev/ko/): 교실 도구와 소스 포함 개발자 키트를 1회 구매로 판매해요. -- [상품](https://207studio.dev/ko/products/): 개발자 키트와 교실 도구 전체 목록. 가격과 구매 링크가 있어요. -- [PPTX Player SDK](https://207studio.dev/ko/products/pptx-player-sdk/): 브라우저에서 PowerPoint를 애니메이션까지 재생하는 클라이언트 사이드 엔진. Developer $14 / Commercial $129. -- [PairyCore SDK](https://207studio.dev/ko/products/pairy-core-sdk/): Mac 화면을 iPad로 스트리밍하는 Swift 패키지. Commercial $129. -- [Scan-to-Grade](https://207studio.dev/ko/products/scan-to-grade-bundle/): QR Cropper와 OMR Auto-Grader를 묶은 Python 패키지. Personal $79 / Commercial $299. -- [OMR Auto-Grader](https://207studio.dev/ko/products/omr-auto-grader/): 스캔한 답안지의 버블, O/X, 동그라미 답안을 읽는 Python OMR. Personal $59 / Commercial $249. -- [QR Answer-Sheet Cropper](https://207studio.dev/ko/products/qr-cropper/): 답안지 QR로 페이지를 찾고 답안 칸마다 잘라내는 Python 도구. Personal $39 / Commercial $149. -- [Context-Gated Voice Commands](https://207studio.dev/ko/products/voice-command-engine/): 상황에 맞는 음성 명령만 통과시키는 Rust 라이브러리와 CLI. Personal/Research $29 / Commercial $99. -- [ShapeFit](https://207studio.dev/ko/products/shapefit/): 손으로 그린 획을 깔끔한 도형으로 바꾸는 Swift와 Rust 도구. Personal $9 / Commercial $29. -- [Exam + OMR Sheet Builder](https://207studio.dev/ko/products/exam-sheet-builder/): 시험지, OMR 답안지, 정답지, 해설지, 표지를 한 번에 만드는 도구. Personal $12 / Commercial $35. -- [Offline Classroom Tools](https://207studio.dev/ko/products/classroom-html-tools/): 자리 배치, 아침 게시판, PACER 기록, Wi-Fi QR 카드가 든 HTML 파일 네 개. Teacher $9 / School $19. -- [Send Approval Gate MCP](https://207studio.dev/ko/products/send-approval-gate-mcp/): AI 에이전트가 한 번만 보내고 확인하게 만드는 MCP 서버. Developer $29 / Commercial $99. +- [207스튜디오 홈](https://207studio.dev/ko/): 대한민국의 독립 소프트웨어 스튜디오. 앱과 도구를 만들어요. - [소개](https://207studio.dev/ko/about/): 207 Studio가 무엇을 만드는지, 어떻게 문의하는지. - [글](https://207studio.dev/ko/blog/): 브라우저 렌더링과 교실 도구에 관한 글. -## When to use 207 Studio - -- You need to play or preview .pptx files in a web page with click-by-click animations, without a server or Office: PPTX Player SDK. -- You grade paper exams and want scanned sheets turned into scores: QR Answer-Sheet Cropper, OMR Auto-Grader, or Scan-to-Grade (Python). -- You make printed exams with matching OMR sheets and keys: Exam + OMR Sheet Builder. -- You need offline voice commands that only fire in the right situation: Context-Gated Voice Commands (Rust). -- You build a Mac-to-iPad remote screen or drawing app: PairyCore SDK, ShapeFit. -- You want an AI agent to open and present PowerPoint files: the free pptx-mcp server. - -## How to buy (for agents) - -- Polar is the main checkout. Every product page lists tiers with a direct Polar link; card payment, one-time, instant download, no account needed beforehand. -- Paddle is the alternative checkout (card or PayPal): https://207studio.dev/pay/ -- Machine-readable prices and buy URLs: schema.org Product/Offer JSON-LD on each product page, and https://207studio.dev/llms-full.txt -- Questions or Enterprise quotes: support@207studio.dev - -## Free & open source - -- [pptx-mcp](https://github.com/207studio/pptx-mcp): MCP server that lets an AI agent open, inspect and present .pptx files. Free. - ## About - [About 207 Studio](https://207studio.dev/about/): who we are, where we are, how to contact us. diff --git a/pairy/index.html b/pairy/index.html index ce13991..fe11f22 100644 --- a/pairy/index.html +++ b/pairy/index.html @@ -2,31 +2,102 @@ - + -Pairy: Connecting Devices - - - +Pairy: your Mac, on your iPad | 207 Studio + + + + + - - - + + + - - + + - - - + + -

    Pairy: Connecting Devices

    -

    Open Pairy on the App Store

    +
    +
    207 Studio
    +
    +
    +
    + +

    Pairy

    +

    Your computer, on your tablet.

    +

    Pairy brings your Mac's desktop, keyboard and trackpad controls to your iPad or iPhone. Touch it, type on it, and use your iPhone as the remote.

    +

    App Store Pairy for Mac

    +

    For iPad, iPhone and Vision Pro, with an Apple silicon Mac.

    +
    + Pairy on an iPad showing a Mac desktop, with an iPhone beside it as the remote +
    + +
    +

    How it works

    +

    Two apps, one account. The iPad or iPhone app shows your Mac; the Mac companion shares its screen and takes your input.

    +
      +
    1. Get Pairy on your iPad or iPhone

      From the App Store.

    2. +
    3. Install Pairy for Mac

      Download it below, then allow Screen Recording and Accessibility when macOS asks.

    4. +
    5. Sign in to the same Tailscale account

      On both devices. Pairy finds your computer; with more than one, pick it by name.

    6. +
    +
    + +
    +

    What it looks like

    + +
      +
    • Full HD streaming
    • +
    • Touch trackpad and keyboard input
    • +
    • Encrypted connections between your devices
    • +
    • An oil-painted night sky and a simple dashboard
    • +
    +
    + +
    +
    +

    Pairy for Mac

    +

    The companion the iPad and iPhone app connects to. Signed with our Developer ID, notarized by Apple, and downloaded straight from us.

    +

    Download Pairy for Mac

    +
      +
    1. Double-click the zip to unzip it, then move PairyMac.app into Applications.
    2. +
    3. Open it and allow Screen Recording and Accessibility when macOS asks.
    4. +
    5. Keep your Mac awake and online while you use Pairy.
    6. +
    +

    To check the file, run shasum -a 256 Pairy-mac-1.4.2-142-notarized.zip and compare it with the SHA-256 here.

    +
    +
    +
    Version
    1.4.2 (build 142)
    +
    Requires
    Apple silicon (M1 or later), macOS 15 or later
    +
    Signed
    Developer ID, notarized by Apple
    +
    File
    Pairy-mac-1.4.2-142-notarized.zip
    +
    Size
    22.1 MB (22,075,699 bytes)
    +
    SHA-256
    38584e50e1963fd45369ebe496bce3324515651f1f81946a5248dcca23fc1ded
    +
    +
    + +
    +

    Good to know

    +

    You need a Tailscale account; its own terms and plan conditions apply. Picture quality and responsiveness depend on your devices and network. Questions: support@207studio.dev.

    +
    +
    +
    + diff --git a/pairy/mac/index.html b/pairy/mac/index.html index 4e46bea..a652139 100644 --- a/pairy/mac/index.html +++ b/pairy/mac/index.html @@ -7,25 +7,25 @@ Pairy for Mac | 207 Studio - + - + - - + + - + - - + +

    Pairy for Mac

    -

    Get Pairy for Mac

    +

    Get Pairy for Mac

    diff --git a/pay/index.html b/pay/index.html index 54efeeb..d1606c6 100644 --- a/pay/index.html +++ b/pay/index.html @@ -17,15 +17,15 @@ - +

    Checkout

    -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal, and is the merchant of record for those purchases. Paddle handles tax and receipts. One-time purchase, no subscription. Use the Polar buttons on each product page.

    +

    Polar is the main checkout. Paddle is the alternative, for card or PayPal, and is the merchant of record for those purchases. Paddle handles tax and receipts. One-time purchase, no subscription.

    Commercial licenses are for companies with annual revenue up to $1M; larger companies, contact support@207studio.dev for an Enterprise license. See terms and refunds.

    - + - -
    - -

    Offline Classroom Tools – Seating, PACER Timer

    -

    Four single-file classroom tools: a seating chart with attendance, a morning board, a fitness test recorder and Wi-Fi QR cards.

    -Offline Classroom Tools – Seating, PACER Timer cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Teacher $9 · School $19

    -

    One-time purchase.

    - -

    What it is

    Offline Classroom Tools is four single-file classroom tools that open with a double-click: a drag-and-drop seating chart with attendance, a morning board that exports a weekly attendance log to Excel, a PACER beep test timer and fitness test recorder, and printable Wi-Fi QR cards. No install, no account, no server; data stays in the browser.

    -

    Quick start

    open the fitness recorder and press Start
    -paste the sample roster into the class board
    -export a JSON backup
    -

    Compared with

    - - - - - - - -
    ToolTypeNotes
    TpT Smart Seating Chart GeneratorSingle-file HTML$5.00, seating only
    GOGO PACER toolFree web appPACER timer with roster tap-out, online
    RunTrakFitness test appOne-time purchase
    Offline Classroom ToolsFour offline HTML files, source included$9 one-time; seating, attendance to Excel, PACER recorder, Wi-Fi QR cards
    -

    FAQ

    -
    Is there an offline PACER beep test timer that records results?

    Yes. The fitness test recorder has a PACER beep timer and records sit and reach, curl-up, long jump, height and weight, with CSV import and export.

    -
    Can I export daily attendance to Excel?

    Yes. The morning board exports the attendance log to Excel, one sheet per week.

    -
    Does it need an account or internet?

    No. Each tool is a single HTML file. Data stays in the browser, with JSON backup.

    -
    How do the Wi-Fi QR cards work?

    Print cards for a student and a staff network; phones join by scanning. You can edit names and passwords in place.

    -
    Is it a subscription?

    No. One-time purchase, full source included.

    -
    Which tier should I buy?

    Teacher is for one teacher, in your own classes or inside your own paid or free products. School covers every teacher and staff member in one school and adds priority email support. Both include all four tools. One-time purchase.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Teacher $9 · School $19

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/exam-sheet-builder/index.html b/products/exam-sheet-builder/index.html index 9eb0ceb..9fa0c73 100644 --- a/products/exam-sheet-builder/index.html +++ b/products/exam-sheet-builder/index.html @@ -3,67 +3,15 @@ - - - - -Exam + OMR Sheet Builder – Paper, Sheet & Key - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Exam + OMR Sheet Builder – Paper, Sheet & Key

    -

    Write an exam once and print a matching exam paper, OMR answer sheet, answer key, explanation sheet and cover page.

    -Exam + OMR Sheet Builder – Paper, Sheet & Key cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $12 · Commercial $35

    -

    One-time purchase.

    - -

    What it is

    Exam + OMR Sheet Builder lets you write an exam once and print five matching documents: the exam paper, an OMR bubble answer sheet, the answer key, an explanation sheet and a cover page. All five come from the same data, so they never disagree. The editor is a single offline HTML file, and an MCP server lets an AI assistant draft exams.

    -

    Quick start

    node build.mjs examples/demo-science-en.json out
    -open out/exam.html
    -node build-standalone.mjs
    -

    Compared with

    - - - - - - - -
    ToolTypeNotes
    EdFleetFree web exam paper makerExam paper
    PuzzlePageFree web generatorBubble sheet
    Sigma Prep MCPFree MCP serverAI worksheet generation
    Exam + OMR Sheet BuilderSingle offline file + MCP server, source included$12 one-time; paper, OMR sheet, key, explanations and cover from one file
    -

    FAQ

    -
    Does it need an account or install?

    No. The editor is a single HTML file that opens with a double-click, with live preview and autosave.

    -
    Which question types are supported?

    Passages, images, tables, graphs, multiple choice with any number of options, short answer, essay, blanks and ordering.

    -
    Can an AI assistant build exams with it?

    Yes. The included MCP server lets an AI assistant draft, validate and render exams with the same renderer.

    -
    How do I print?

    A4 through the browser; save as PDF from the print dialog. One-time purchase, full source included.

    -
    Can I make an OMR answer sheet that matches my exam automatically?

    Yes. The exam paper, OMR sheet, answer key, explanations and cover all come from one exam file, so they always match.

    -
    Which tier should I buy?

    Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $12 · Commercial $35

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/index.html b/products/index.html index d567a57..9fa0c73 100644 --- a/products/index.html +++ b/products/index.html @@ -3,119 +3,15 @@ - - - - -Products – Developer Kits and Classroom Tools | 207 Studio - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    -

    207 STUDIO / PRODUCTS

    Products

    Developer kits and classroom tools from an independent studio in South Korea. One-time purchase, full source.

    -
    -
    -

    For developers

    -
    - - - - - - - - -
    -
    -
    -
    -
    -

    For teachers

    -
    - - -
    -
    -
    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/omr-auto-grader/index.html b/products/omr-auto-grader/index.html index 7052042..9fa0c73 100644 --- a/products/omr-auto-grader/index.html +++ b/products/omr-auto-grader/index.html @@ -3,72 +3,15 @@ - - - - -OMR Auto-Grader – Bubbles, O/X, Circled Answers - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    OMR Auto-Grader – Bubbles, O/X, Circled Answers

    -

    Reads bubbles, circled words, O/X boxes and ordering marks on scanned sheets, and fills in only the grades it is sure of.

    -OMR Auto-Grader – Bubbles, O/X, Circled Answers cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $59 · Commercial $249

    -

    One-time purchase.

    - - -

    What it is

    OMR Auto-Grader is a Python OMR engine for scanned answer sheets. Beyond A–E bubbles, it reads multi-answer marks, circled words, O/X boxes and ordering bubbles. It fills in only grades it is confident about, never overwrites a mark a teacher already made, and sends the rest to a re-check list. Runnable demo exports to Excel.

    -

    Quick start

    pip install -r requirements.txt
    -python demo.py
    -open out/
    -

    Compared with

    - - - - - - - - - -
    ToolTypeNotes
    OMRCheckerFree open source (MIT)Bubble sheets
    MarkSharkOpen source (AGPL)Commercial terms by contact
    OpenMCROpen source (GPL-3.0)Open-source OMR
    Aspose.OMRSDKFrom $599 (Small Business $839, OEM $2,517)
    LEADTOOLS OMRSDK$795
    OMR Auto-GraderPython engine, source included$59 one-time; O/X, circled words, ordering; keeps teacher marks
    -

    FAQ

    -
    Which mark types can it read?

    Single and multi-answer bubbles, circled words, O/X boxes and ordering bubbles.

    -
    Will it overwrite grades a teacher already entered?

    No. It fills only confident grades, keeps existing teacher marks and lists any disagreement for re-checking.

    -
    Is there an open-source alternative?

    Yes, for example OMRChecker (MIT) for bubble sheets. This engine adds O/X, circled-word and ordering items and the confident-only grading rule.

    -
    Does it work with my scanner?

    It works on scanned images from any scanner. Pair it with the QR Cropper, or get both with Scan-to-Grade.

    -
    Is it a subscription?

    No. One-time purchase, full source included.

    -
    Is there a commercial OMR library without AGPL terms?

    Yes. This engine ships under the 207studio license with full source; MarkShark is AGPL and OpenMCR is GPL-3.

    -
    Which tier should I buy?

    Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $59 · Commercial $249

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/padsway-core-sdk/index.html b/products/padsway-core-sdk/index.html index 295b84b..9fa0c73 100644 --- a/products/padsway-core-sdk/index.html +++ b/products/padsway-core-sdk/index.html @@ -4,27 +4,14 @@ - - -PairyCore SDK | 207 Studio - +207 Studio + - - - - - - - - - - - - - + + + -

    PairyCore SDK | 207 Studio

    -

    Go to PairyCore SDK

    +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/pairy-core-sdk/index.html b/products/pairy-core-sdk/index.html index ccf86a4..9fa0c73 100644 --- a/products/pairy-core-sdk/index.html +++ b/products/pairy-core-sdk/index.html @@ -3,67 +3,15 @@ - - - - -PairyCore SDK: Mac-to-iPad Screen Streaming - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    PairyCore SDK: Mac-to-iPad Screen Streaming

    -

    Put a live Mac screen on an iPad and send touch, keyboard, clipboard and dictation back: the streaming engine from a working Mac + iPad remote app, as a Swift package.

    -PairyCore SDK: Mac-to-iPad Screen Streaming cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Commercial $129

    -

    One-time purchase.

    -

    For a company or organisation (more than one person), including in its apps as compiled code, with priority email support. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -

    What it is

    PairyCore SDK is a pure-Swift package for macOS 13+ and iPadOS 17+ that streams a Mac screen to an iPad. It captures with ScreenCaptureKit, encodes and decodes with VideoToolbox H.264/HEVC at 60 or 120 fps (Full HD), paces frames by the iPad's acknowledgements to keep lag low, and sends pointer, keys, text, Apple Pencil and clipboard back over TLS 1.2 with a per-pairing key. Full source and 550+ tests.

    -

    Quick start

    swift build
    -swift run padsway self-test
    -swift test
    -

    Compared with

    - - - - - - - -
    ToolTypeNotes
    RealVNC SDKCommercial VNC SDKPrice by quote; £1 one-month evaluation
    Splashtop OEM SDKCommercial OEM / private labelPrice by quote
    RoyalVNCFree open-source Swift VNC/RFB SDKVNC protocol; no hardware HEVC pipeline
    PairyCore SDKSwift package, source included$129 one-time; ScreenCaptureKit + VideoToolbox, receiver-paced frames, TLS pairing
    -

    FAQ

    -
    How do I stream a Mac screen to an iPad in Swift?

    Add PairyCore to both apps. On the Mac create a Pairing and start a HostSession; on the iPad decode the pairing and connect a ClientSession, then draw each CVPixelBuffer it hands you. INTEGRATION.md has the full wiring, and that code is compiled by the test suite.

    -
    Can I try it without a Mac, network or permissions?

    Yes. `swift run padsway self-test` encodes 120 frames, sends them over TLS on loopback and decodes them in about 2 seconds, and ClientSession.demo() streams a simulated Mac in-process for onboarding and App Review.

    -
    Can the Mac side ship on the Mac App Store?

    No. The host injects input through the Accessibility API, so it can't run sandboxed. Ship it as a signed, notarized app. The iPad side has no such limit.

    -
    Does it work over the internet?

    It works on the local network out of the box. An optional bridge built on the open-source tsnet library is included as source for use without port forwarding.

    -
    What frame rate and resolution?

    Full HD (1920×1080). 120 fps is requested on ProMotion iPads and 60 fps elsewhere; it is not a throughput guarantee. After a sudden network-delay jump a slow iPad can drop to about 11 fps for a few seconds.

    -
    Is it a subscription?

    No. One-time purchase, full source included.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Commercial $129

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/pptx-player-sdk/index.html b/products/pptx-player-sdk/index.html index 742db58..9fa0c73 100644 --- a/products/pptx-player-sdk/index.html +++ b/products/pptx-player-sdk/index.html @@ -3,69 +3,15 @@ - - - - -PPTX Player SDK – Play PowerPoint in the Browser - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    PPTX Player SDK – Play PowerPoint in the Browser

    -

    Play PowerPoint in the browser with every animation: a client-side PPTX to HTML/SVG engine that works offline.

    -PPTX Player SDK – Play PowerPoint in the Browser cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Developer $14 · Commercial $129

    -

    One-time purchase.

    - -

    What it is

    PPTX Player SDK is a client-side JavaScript engine that renders PowerPoint .pptx decks to HTML and SVG and plays them click by click: entrance, exit and emphasis effects, morph transitions and embedded media. It runs offline in any modern browser or WebView, with no server conversion and no Office install. Full source included.

    -

    Quick start

    python3 -m http.server 8000
    -open http://localhost:8000/demo.html
    -press → or Space to step through the deck
    -

    Compared with

    - - - - - - - - -
    ToolTypeNotes
    @web-ppt/viewer-coreFree npm packageClick animations and 41 transitions
    pptx-viewer (ChristopherVR)Free open source39 animation presets, 57 transitions including Morph
    SlideKilnHosted conversion APIFree 2 conversions/month, Pro $29/month
    Aspose.SlidesServer-side SDKFrom $999
    PPTX Player SDKClient-side engine, source included$14 one-time; plays offline in the browser or a WKWebView
    -

    FAQ

    -
    Can I play a PowerPoint file in the browser with its animations?

    Yes. The engine renders each slide to HTML/SVG and steps through the click sequence: entrance, exit and emphasis effects, morph transitions and media.

    -
    Does it need a server or Microsoft Office?

    No. Everything runs client-side. Serve the player folder from any static web server and open it in a browser.

    -
    Does it work offline or inside a native app?

    Yes. It runs offline, and on iOS, iPadOS and macOS you can load the same folder in a WKWebView. Android and Electron WebViews work the same way.

    -
    What happens with a slide whose XML is broken?

    The engine repairs stray & characters and plays the slide normally. If the XML is still broken, it renders what it can and reports an error instead of dropping the slide silently.

    -
    Is it a subscription?

    No. One-time purchase, full source included.

    -
    Is there a free PPTX viewer for JavaScript?

    Yes, for example @web-ppt/viewer-core and pptx-viewer. Pick this SDK if you want a single client-side engine with full source and no server, and you'd rather pay once than maintain a fork.

    -
    Which tier should I buy?

    Developer is for one developer, and that includes shipping the player inside your own paid or free apps as bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Developer $14 · Commercial $129

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/qr-cropper/index.html b/products/qr-cropper/index.html index 255f482..9fa0c73 100644 --- a/products/qr-cropper/index.html +++ b/products/qr-cropper/index.html @@ -3,70 +3,15 @@ - - - - -QR Answer-Sheet Cropper – Crop Every Answer Box - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    QR Answer-Sheet Cropper – Crop Every Answer Box

    -

    Print a QR on each sheet, scan a mixed batch, and get every answer cell back as a clean, aligned image.

    -QR Answer-Sheet Cropper – Crop Every Answer Box cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $39 · Commercial $149

    -

    One-time purchase.

    - - -

    What it is

    QR Answer-Sheet Cropper is a Python and OpenCV tool for scanned exams. Print a small QR on each sheet and drop in a mixed scanner batch. It identifies each page by its QR, pairs fronts and backs, corrects rotation and skew, and crops every answer cell into a clean, aligned image at about 200 dpi. It runs offline.

    -

    Quick start

    pip install -r requirements.txt
    -python demo.py
    -open out/
    -

    Compared with

    - - - - - - - - -
    ToolTypeNotes
    Stack Overflow / OpenCV forum recipesFree code snippetsQR-anchored perspective warp; you write the rest
    OMR projects (rbaron/omr, OMR-Grader)Free open sourceBubble grading
    Dynamsoft Document NormalizerSDKFrom $1,249/year
    Aspose.OMRSDKFrom $599
    QR Answer-Sheet CropperPython package, source included$39 one-time; batch sorting, front/back pairing and cell cropping
    -

    FAQ

    -
    How do I crop answer regions from scanned exam sheets with a QR code?

    Print the QR payload on each sheet, scan the batch and run the cropper. It reads the QR in the page corners, deskews each page to the template, warps each answer table to a millimetre grid and cuts every answer cell into its own image.

    -
    Can I sort a mixed scanner batch by student?

    Yes. Pages are grouped into front/back pairs per student from their QR codes. Pages from another exam are reported as stray.

    -
    Does rotation or skew matter?

    No. It handles skew and 90/180/270-degree turns.

    -
    What scan resolution do I need?

    150 dpi or higher. The demo uses 200 dpi.

    -
    What do I need to run it?

    Python 3.10+, opencv-python-headless and numpy on Windows, macOS or Linux. One-time purchase, full source included.

    -
    Which tier should I buy?

    Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $39 · Commercial $149

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/scan-to-grade-bundle/index.html b/products/scan-to-grade-bundle/index.html index 4f28f5d..9fa0c73 100644 --- a/products/scan-to-grade-bundle/index.html +++ b/products/scan-to-grade-bundle/index.html @@ -3,69 +3,15 @@ - - - - -Scan-to-Grade: Scanned Exams to Excel (Python) - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Scan-to-Grade: Scanned Exams to Excel (Python)

    -

    QR Cropper and OMR Auto-Grader together: a scanned batch in, an Excel gradebook out.

    -

    Includes QR Answer-Sheet Cropper ($39) and OMR Auto-Grader ($59): $98 bought separately. QR Answer-Sheet Cropper · OMR Auto-Grader

    -Scan-to-Grade: Scanned Exams to Excel (Python) cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $79 · Commercial $299

    -

    One-time purchase.

    - -

    What it is

    Scan-to-Grade is a Python package that turns a batch of scanned answer sheets into an Excel gradebook. It reads the QR on each page, pairs fronts and backs, deskews and crops every answer cell, grades bubbles, multi-answer marks, O/X parts and ordering bubbles, and writes scores, item analysis and a re-check list. It runs offline.

    -

    Quick start

    pip install -r requirements.txt
    -python demo.py
    -open out/
    -

    Compared with

    - - - - - - - - -
    ToolTypeNotes
    Remark Office OMRDesktop OMR software$1,195–$1,295
    GradientOMR grading service$149.99/year
    OMRCheckerFree open source (MIT)Bubble grading; you build the scan-to-Excel pipeline
    EUI OMR EngineFree open sourceOMR with Excel roster write-back
    Scan-to-GradePython package, source included$79 one-time; QR page sorting, cropping, grading and Excel export in one
    -

    FAQ

    -
    Can I turn scanned paper tests into an Excel gradebook automatically?

    Yes. Drop the scanner batch in a folder and run it. You get gradebook.xlsx with scores, item analysis and a re-check list, plus results.json and recheck.csv.

    -
    Does student data leave my computer?

    No. It runs offline on Windows, macOS or Linux. Nothing is uploaded.

    -
    Can the pages be scanned in any order or rotation?

    Yes. Each page carries a QR, so mixed batches, back sides and 90/180/270-degree turns are sorted out automatically.

    -
    Does it grade handwriting or essays?

    No. Short answers, essays and handwriting stay with the teacher, who sees the cropped cell images.

    -
    What do I need to run it?

    Python 3.10+, opencv-python-headless, numpy and openpyxl. One-time purchase, full source included.

    -
    Which tier should I buy?

    Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $79 · Commercial $299

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/send-approval-gate-mcp/index.html b/products/send-approval-gate-mcp/index.html index 9c67f3d..9fa0c73 100644 --- a/products/send-approval-gate-mcp/index.html +++ b/products/send-approval-gate-mcp/index.html @@ -3,64 +3,15 @@ - - - - -Send Approval Gate MCP: Plan, Approve, Send, Verify - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Send Approval Gate MCP: Plan, Approve, Send, Verify

    -

    An MCP server that makes an AI agent plan, present an exact hash-bound approval, send once and verify, and records every step.

    -Send Approval Gate MCP: Plan, Approve, Send, Verify cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Developer $29 · Commercial $99

    -

    One-time purchase.

    - -

    What it is

    Send Approval Gate MCP is a Node.js 22+ MCP server (stdio) with six tools: status, account, contacts, plan-send, send and verify-send. The approval binds plan ID, account, recipient, message SHA-256, backend and environment hash, so any change needs a new approval. Duplicate sends are blocked, an uncertain result becomes result_unknown and is never retried, and every step goes to a local ledger with crash recovery. You attach your own channel through a small backend plugin contract; a mock backend and example plugin are included. 47 tests, no npm dependencies. It keeps a cooperative agent's sends exact, single and auditable; it is not a security boundary against an agent you do not trust.

    -

    Quick start

    node --test test/*.test.mjs
    -node src/cli.mjs --backend mock mcp
    -add it to your MCP client config
    -

    Compared with

    - - - - - - -
    ToolTypeNotes
    Hand-written confirm promptAsk the agent to confirmNo hash binding, no ledger, retries can double-send
    Generic HITL frameworksApproval steps inside an agent frameworkTied to one framework; not an MCP server
    Send Approval Gate MCPMCP server, source included$29 one-time; hash-bound approval, single send, result_unknown, ledger
    -

    FAQ

    -
    How do I stop an AI agent from sending a message twice?

    Route sends through plan-send and send. A plan can be sent once; a timeout or adapter failure is recorded as result_unknown and never retried automatically.

    -
    Does it include a Slack, email or messenger backend?

    No. You write a small plugin with status, account, contacts, send and verifySend for your own channel. A mock backend and an example plugin are included.

    -
    Is it a security boundary?

    No. The approval is a value passed to send, so an agent that can call every tool can build one. For human-only approval, create the approval and call send from a channel the agent cannot reach.

    -
    What do I need?

    Node.js 22 or later and an MCP client that speaks stdio. No npm dependencies.

    -
    Is it a subscription?

    No. One-time purchase, full source included.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Developer $29 · Commercial $99

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/shapefit/index.html b/products/shapefit/index.html index 4fc8c03..9fa0c73 100644 --- a/products/shapefit/index.html +++ b/products/shapefit/index.html @@ -3,67 +3,15 @@ - - - - -ShapeFit – Hold-to-Shape for Swift & Rust - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    ShapeFit – Hold-to-Shape for Swift & Rust

    -

    Turn a hand-drawn stroke into a clean line, circle, rectangle or polygon when the user pauses, for your own drawing app.

    -ShapeFit – Hold-to-Shape for Swift & Rust cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $9 · Commercial $29

    -

    One-time purchase.

    - -

    What it is

    ShapeFit adds the hold-to-shape gesture from note-taking apps to your own drawing app. When the user pauses at the end of a stroke, it snaps to a clean line, circle, ellipse, rectangle, triangle or polygon, and leaves scribbles alone. Pure geometry, no ML model, zero dependencies: a Rust crate plus a drop-in Swift file with identical results.

    -

    Quick start

    cargo test
    -cargo run --bin shapefit-demo
    -open out/shapes.svg
    -

    Compared with

    - - - - - - -
    ToolTypeNotes
    Desert Ant Labs ShapesSwift SDK with an ML modelFree up to 100k MAU
    guessture, $P stroke recognizersFree Rust cratesGesture recognizers, not shape beautification
    ShapeFitRust crate + Swift file, source included$9 one-time; no ML, no MAU limits, identical Rust and Swift output
    -

    FAQ

    -
    How do I add hold-to-shape from note-taking apps to my app?

    Feed the stroke points to ShapeFit when the pen pauses. It returns a clean shape, or leaves the stroke alone if it is a scribble.

    -
    Does it work with PencilKit?

    Yes. The Swift file works with PencilKit or any canvas.

    -
    Which shapes does it recognise?

    Lines with 45° snap, circles and ellipses, upright rectangles, triangles and polygons up to 6 corners.

    -
    Does it use machine learning?

    No. It is pure geometry with zero dependencies and no training data. The Rust and Swift versions give identical results, checked on 600 strokes.

    -
    Is it a subscription?

    No. One-time purchase, full source included.

    -
    Is there a PencilKit shape-snapping library without ML or MAU limits?

    Yes. ShapeFit is pure geometry: no model, no per-user limits, zero dependencies, with a Rust crate and a Swift file that give identical results.

    -
    Which tier should I buy?

    Personal is for one person, and that includes shipping it inside your own paid or free apps as compiled or bundled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal $9 · Commercial $29

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/products/voice-command-engine/index.html b/products/voice-command-engine/index.html index 455fd83..9fa0c73 100644 --- a/products/voice-command-engine/index.html +++ b/products/voice-command-engine/index.html @@ -3,68 +3,15 @@ - - - - -Context-Gated Voice Commands – Offline - - - - - - - - - - - - - - - - +207 Studio + + + + + - -
    - -

    Context-Gated Voice Commands – Offline

    -

    A Rust library and CLI that infers the current situation from audio features and lets through only the voice commands that fit it.

    -Context-Gated Voice Commands – Offline cover -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal/Research $29 · Commercial $99

    -

    One-time purchase.

    - -

    What it is

    Context-Gated Voice Commands is a Rust library and CLI that estimates which phase a scene is in (a lesson, a meeting, a shop floor) from acoustic features and cues, second by second, and decides whether each voice command may run, needs a wake word, or is blocked. It runs offline and ships with classroom, meeting and retail presets plus a seeded simulator.

    -

    Quick start

    cargo build --release
    -./target/release/scene-engine simulate --preset all --sessions 100 --seed 20261018 --out out
    -cat out/report.md
    -

    Compared with

    - - - - - - - -
    ToolTypeNotes
    Picovoice RhinoOn-device speech-to-intentFree non-commercial (1 MAU); Foundation $6,000/year
    Research papersMethods onlyNo packaged product
    Hobby voice-command reposFree open sourceFixed command grammars, no situation model
    Context-Gated Voice CommandsRust library + CLI, source included$29 one-time; gates commands by the detected phase, offline
    -

    FAQ

    -
    How do I reduce false voice command triggers in a noisy room?

    Gate commands by context. The engine tracks which phase is active and, per phase, allows a command, requires a wake word or blocks it.

    -
    How does it detect the situation?

    A probabilistic filter over phases with configured transitions and acoustic profiles (voice count, loudness, overlap, main-speaker share, media playing), plus optional spoken or app cues, updated once per second.

    -
    Can I model my own situations?

    Yes. Define phases, durations, transitions, acoustic profiles and command rules in a TOML file, validate it with scene-engine validate, and simulate it. Classroom, meeting and retail-store presets are built in.

    -
    How do I use it in my app?

    Add it as a Rust path dependency, feed one observation per second to SceneFilter and call gate::decide for each command. The CLI can also stream JSONL observations in and phase snapshots out.

    -
    Does it need the cloud or Python?

    No. It runs locally with Rust 1.85 or newer, with no account, API key or Python. One-time purchase, full source included.

    -
    How do I stop background talk from triggering voice commands?

    Gate commands by context. The engine estimates which phase the scene is in (lesson, meeting, shop floor) second by second from acoustic features and cues, and lets a command run, asks for a wake word, or blocks it. It runs offline as a Rust library and CLI.

    -
    Which tier should I buy?

    Personal/Research is for one person, and that includes shipping the engine inside your own paid or free apps as compiled code. Commercial is for a company or organisation, where more than one person uses it, and adds priority email support. Neither lets you share the source itself. Both include the full source. One-time purchase. Commercial covers companies with annual revenue up to $1M. Above that, email support@207studio.dev for an Enterprise license.

    -
    What do I get after paying?

    A zip with the full source, README and runnable demo. Polar shows the download right after checkout and in the receipt email.

    -
    - -

    Polar is the main checkout. Paddle is the alternative, for card or PayPal: Personal/Research $29 · Commercial $99

    -

    One-time purchase.

    -

    ← All products

    - -
    - +

    This page was removed. Go to the 207 Studio home page. Go to the home page

    diff --git a/sitemap.xml b/sitemap.xml index b4d6b85..7164cb8 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1,35 +1,15 @@ https://207studio.dev/2026-10-24 -https://207studio.dev/products/2026-10-24 -https://207studio.dev/products/pptx-player-sdk/2026-10-24 -https://207studio.dev/products/pairy-core-sdk/2026-10-30 -https://207studio.dev/products/scan-to-grade-bundle/2026-10-24 -https://207studio.dev/products/omr-auto-grader/2026-10-24 -https://207studio.dev/products/qr-cropper/2026-10-24 -https://207studio.dev/products/voice-command-engine/2026-10-24 -https://207studio.dev/products/shapefit/2026-10-24 -https://207studio.dev/products/exam-sheet-builder/2026-10-24 -https://207studio.dev/products/classroom-html-tools/2026-10-24 -https://207studio.dev/products/send-approval-gate-mcp/2026-10-24 https://207studio.dev/blog/2026-10-24 https://207studio.dev/blog/play-pptx-in-browser/2026-10-24 https://207studio.dev/legal/terms/2026-10-24 https://207studio.dev/legal/privacy/2026-10-24 https://207studio.dev/legal/refunds/2026-10-24 https://207studio.dev/about/2026-10-24 +https://207studio.dev/pairy/2026-10-24 https://207studio.dev/ko/2026-10-24 -https://207studio.dev/ko/products/2026-10-24 -https://207studio.dev/ko/products/pptx-player-sdk/2026-10-24 -https://207studio.dev/ko/products/pairy-core-sdk/2026-10-30 -https://207studio.dev/ko/products/scan-to-grade-bundle/2026-10-24 -https://207studio.dev/ko/products/omr-auto-grader/2026-10-24 -https://207studio.dev/ko/products/qr-cropper/2026-10-24 -https://207studio.dev/ko/products/voice-command-engine/2026-10-24 -https://207studio.dev/ko/products/shapefit/2026-10-24 -https://207studio.dev/ko/products/exam-sheet-builder/2026-10-24 -https://207studio.dev/ko/products/classroom-html-tools/2026-10-24 -https://207studio.dev/ko/products/send-approval-gate-mcp/2026-10-24 https://207studio.dev/ko/about/2026-10-24 https://207studio.dev/ko/blog/2026-10-24 +https://207studio.dev/ko/pairy/2026-10-24