Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions 404.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ <h1>404: page not found</h1>

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
</pre>
</main><footer class="site-footer page-foot-hand"><div class="foot-grid"><div class="foot-brand"><a class="brand" href="/">207 Studio</a><p>Software for making and teaching.</p></div><nav class="foot-col" aria-label="Work"><h2>Work</h2><a href="/products/">Products</a><a href="/blog/">Writing</a><a href="https://apps.apple.com/app/id6815584225" target="_blank" rel="noopener">Pairy</a></nav><nav class="foot-col" aria-label="Studio"><h2>Studio</h2><a href="/about/">About</a><a href="mailto:support@207studio.dev">Support</a><a href="/llms.txt">llms.txt</a></nav><nav class="foot-col" aria-label="Legal"><h2>Legal</h2><a href="/legal/privacy/">Privacy Policy</a><a href="/legal/terms/">Terms of Use</a><a href="/legal/refunds/">Refund Policy</a></nav></div><img class="foot-word" src="/assets/footer-207.svg" alt="" aria-hidden="true" width="1513" height="700" loading="lazy" decoding="async"><div class="foot-bottom"><span>© 2026 207 Studio · South Korea</span></div></footer>
</main><footer class="site-footer page-foot-hand"><div class="foot-grid"><div class="foot-brand"><a class="brand" href="/">207 Studio</a><p>Software for making and teaching.</p></div><nav class="foot-col" aria-label="Work"><h2>Work</h2><a href="/blog/">Writing</a><a href="https://apps.apple.com/app/id6815584225" target="_blank" rel="noopener">Pairy</a></nav><nav class="foot-col" aria-label="Studio"><h2>Studio</h2><a href="/about/">About</a><a href="mailto:support@207studio.dev">Support</a><a href="/llms.txt">llms.txt</a></nav><nav class="foot-col" aria-label="Legal"><h2>Legal</h2><a href="/legal/privacy/">Privacy Policy</a><a href="/legal/terms/">Terms of Use</a><a href="/legal/refunds/">Refund Policy</a></nav></div><img class="foot-word" src="/assets/footer-207.svg" alt="" aria-hidden="true" width="1513" height="700" loading="lazy" decoding="async"><div class="foot-bottom"><span>© 2026 207 Studio · South Korea</span></div></footer>
</body>
</html>
9 changes: 0 additions & 9 deletions _blog/play-pptx-in-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?**
Expand All @@ -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.
2 changes: 1 addition & 1 deletion _blog/posts.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
]
96 changes: 96 additions & 0 deletions _products/apps.json
Original file line number Diff line number Diff line change
@@ -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)을 엔진 하나로"]
}
}
]
}
4 changes: 2 additions & 2 deletions _products/blog-gen.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,10 @@ ${twImage ? `<meta name="twitter:image" content="${esc(twImage)}">\n` : ''}<link

function header(current) {
const blog = current === 'blog' ? ' aria-current="page"' : '';
return `<header class="site-header wrap"><a class="brand" href="/"><span class="brand-mark">207</span><span>STUDIO</span></a><nav aria-label="Main navigation"><a href="/">Home</a><a href="/products/">Products</a><a href="/blog/"${blog}>Writing</a></nav></header>`;
return `<header class="site-header wrap"><a class="brand" href="/"><span class="brand-mark">207</span><span>STUDIO</span></a><nav aria-label="Main navigation"><a href="/">Home</a><a href="/blog/"${blog}>Writing</a></nav></header>`;
}

const FOOT_GRID = '<div class="foot-grid"><div class="foot-brand"><a class="brand" href="/">207 Studio</a><p>Software for making and teaching.</p></div><nav class="foot-col" aria-label="Work"><h2>Work</h2><a href="/products/">Products</a><a href="/blog/">Writing</a><a href="https://apps.apple.com/app/id6815584225" target="_blank" rel="noopener">Pairy</a></nav><nav class="foot-col" aria-label="Studio"><h2>Studio</h2><a href="/about/">About</a><a href="mailto:support@207studio.dev">Support</a><a href="/llms.txt">llms.txt</a></nav><nav class="foot-col" aria-label="Legal"><h2>Legal</h2><a href="/legal/privacy/">Privacy Policy</a><a href="/legal/terms/">Terms of Use</a><a href="/legal/refunds/">Refund Policy</a></nav></div><img class="foot-word" src="/assets/footer-207.svg" alt="" aria-hidden="true" width="1513" height="700" loading="lazy" decoding="async">';
const FOOT_GRID = '<div class="foot-grid"><div class="foot-brand"><a class="brand" href="/">207 Studio</a><p>Software for making and teaching.</p></div><nav class="foot-col" aria-label="Work"><h2>Work</h2><a href="/blog/">Writing</a><a href="https://apps.apple.com/app/id6815584225" target="_blank" rel="noopener">Pairy</a></nav><nav class="foot-col" aria-label="Studio"><h2>Studio</h2><a href="/about/">About</a><a href="mailto:support@207studio.dev">Support</a><a href="/llms.txt">llms.txt</a></nav><nav class="foot-col" aria-label="Legal"><h2>Legal</h2><a href="/legal/privacy/">Privacy Policy</a><a href="/legal/terms/">Terms of Use</a><a href="/legal/refunds/">Refund Policy</a></nav></div><img class="foot-word" src="/assets/footer-207.svg" alt="" aria-hidden="true" width="1513" height="700" loading="lazy" decoding="async">';

function footer(ko) {
const lang = ko ? `<a href="${esc(ko)}" lang="ko" hreflang="ko">\ud55c\uad6d\uc5b4</a>` : '';
Expand Down
9 changes: 8 additions & 1 deletion _products/gen/check_llms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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]
Expand Down
Loading
Loading