everykoreans.com. 빌드 단계가 없는 정적 사이트다 — main 에 밀면 1~2분 뒤
GitHub Pages 가 올린다.
로컬에서 보려면:
npx --yes http-server . -p 5500CI(.github/workflows/check.yml)가 PR 과 main 푸시마다 아래 검사를 전부
돌린다 — 자국이 낡았거나 첫 쪽 숫자가 틀리거나 레슨 글에 줄바꿈 대신
「\n」 두 글자가 들어가 있으면 빨간불이 켜진다. 그래도 밀기 전에 로컬에서
먼저 돌리는 편이 한 바퀴를 아낀다.
node tools/build-grammar.mjs # 글에서 찾아낼 문법 (예문 이름을 고쳤으면 반드시)
node tools/build-pages.mjs # 검색용 정적 쪽 (예문 자료를 고쳤으면 반드시)
node tools/stamp.mjs # 캐시 자국을 새로 찍는다 (자료·코드를 고쳤으면 반드시)
node tools/check-data.mjs # 자료 전체 크로스체크
node tools/check-courses.mjs # 코스·레슨 (레슨을 고쳤거나 더했으면 반드시)
node tools/check-honorific.mjs # 존댓말 활용 (hon: true 코스를 고쳤거나 더했으면 반드시)
node tools/check-sentences.mjs # 예문 표현
node tools/check-topik.mjs # TOPIK I
node tools/check-topik2.mjs docs/topik2-all50.json # TOPIK II
node tools/check-glossary.mjs # 낱말 뜻풀이
node tools/check-blog.mjs # 블로그 글 (글을 더했거나 고쳤으면 반드시)
node tools/check-style.mjs docs/topik2-round2.json # 출제·문법 (새 문항을 받았을 때)
node tools/check-newwords.mjs docs/topik2-round2.json # 처음 보는 낱말 (사람이 눈으로)
node --check app.js && node --check app.module.jsstamp.mjs 를 빼먹으면 고쳐서 올려도 사용자에게는 한동안 예전 화면이
나온다. 실제로 겪었다 — 주소 라우팅을 고쳐 올렸는데 「그대로인데?」 하는
일이 났고, 코드는 멀쩡했고 브라우저가 예전 파일을 쥐고 있던 것이었다.
찍혔는지만 보고 싶으면:
node tools/stamp.mjs --check # 낡았으면 종료 코드 1GitHub Pages 는 캐시 머리글을 우리가 못 정한다. app.js 를 그냥 부르면
배포한 뒤에도 브라우저가 한동안 예전 파일을 쓴다. 주소 뒤에 내용에서 뽑은
?v= 를 붙이면 내용이 바뀔 때만 주소가 바뀌므로, 바뀐 것만 새로 받는다.
모듈이 부르는 파일까지 함께 찍는다. app.module.js 만 찍으면 그 안의
import './sentences.js' 는 예전 주소 그대로라 새 코드가 예전 자료를 읽는
어정쩡한 상태가 된다. 그쪽이 더 나쁘다 — 코드가 새것이라 오류도 안 난다.
자국은 날짜나 회차가 아니라 파일 내용에서 뽑는다. 날짜로 찍으면 안 바뀐 파일까지 새로 받게 되고, 손으로 올리는 값이면 잊어버린다.
| 파일 | 만드는 것 | 원본 |
|---|---|---|
topik2.js |
tools/build-topik2.mjs |
docs/topik2-all50.json |
sentence/ · compare/ · course/ · lesson/ · topik-writing/ · topik-reading/ · topik-listening/ · blog/(rss.xml 포함) · sitemap.xml |
tools/build-pages.mjs |
sentences*.js · courses*.js · topik-writing.js · topik.js · topik2.js · topik-listening.js · blog.js |
glossary.js · glossary-<말>.js |
tools/build-glossary.mjs |
docs/glossary.json (+ glossary-krdict.json) |
grammar.js |
tools/build-grammar.mjs |
sentences.js 의 문법 이름 |
courses-lite.js |
tools/build-courses-lite.mjs |
courses.js — 앱이 받는 코스 목록. 중·고급 레슨 본문을 빼서 1.5MB → 0.4MB. 코스를 고쳤으면 반드시 (낡으면 check-courses 가 멈춘다) |
docs/page-mod.json |
tools/build-pages.mjs |
구운 쪽의 해시와 날짜 (사이트맵 lastmod 용) |
docs/glossary-krdict.json |
tools/build-krdict-glossary.mjs |
국립국어원 내려받기 자료 |
favicon-32.png · icon-180.png |
tools/build-icons.py |
logo.png |
privacy.html |
tools/build-privacy.js |
앱 저장소의 docs/privacy-policy.md |
vendor/ |
tools/vendor.mjs |
바깥 라이브러리 |
extension/data/ |
tools/build-extension.mjs |
사전·문법·표현 (저장소에는 안 넣는다 — 아래 「크롬 익스텐션」) |
extension/icons/ |
tools/build-icons.py |
logo.png |
화면 전환을 전부 해시(#learn/sentence/23-1)로 하므로 크롤러에게 이
사이트는 index.html 한 쪽이다. 해시 뒤는 서버로 가지도 않아서, 표현
290개를 쌓아 두고도 검색에는 한 글자도 안 걸렸다.
그래서 tools/build-pages.mjs 가 표현마다 진짜 주소를 가진 정적 쪽을 뽑는다.
/sentence/ 표현 290개를 한 번에 거는 목록 (크롤러의 들머리)
/sentence/23-1.html 표현 하나 — 뜻·형태·주의할 점·예문·대화문
그 쪽에서 앱 화면(/#learn/sentence/23-1)으로 보낸다. 검색은 정적 쪽이
받고, 연습은 앱이 맡는다.
- 쪽마다 CSS 를 박아 넣는다. 따로 빼면
stamp.mjs의 자국 대상이 290개로 불어나고, 검색에서 들어온 첫 화면이 한 번에 안 그려진다. - 글꼴도
vendor/pretendard를 안 부른다. 처음 들어온 사람에게 웹폰트 수백 KB 를 물리는 것보다 기기 글꼴로 즉시 읽히는 편이 낫다. hreflang은 짝이 있을 때만 건다. 언어를 주소가 아니라 화면에서 가르므로 영어 쪽 주소가 대부분 없다. 없는 주소를 적으면 구글이 그 줄을 통째로 버린다. 다만 블로그 글은 말마다 제 주소를 가지므로(blog.js의lang과alt), 짝이 실제로 있는 글에만hreflang이 붙는다.- 예문 자료를 고쳤으면
build-pages.mjs를 다시 돌린다. 안 돌리면 지운 표현의 쪽이 남아 앱에 없는 것이 검색에 걸린다.
시험을 풀다 모르는 낱말을 눌러 단어장에 담을 때, 뜻 칸이 늘 비어 있었다. 빈 칸을 스무 개 받아 놓고 하나씩 채우는 사람은 없다.
번역기를 부르지 않고 사전을 들고 다닌다. 번역기에는 열쇠가 있어야 하는데 이 사이트는 정적이라 열쇠를 둘 데가 없다 — 코드에 넣으면 그대로 공개된다. 대신 우리 지문에 나오는 낱말만 미리 적어 둔다. 지문에 없는 말은 눌릴 일이 없으니 온 세상 낱말을 담을 까닭도 없다.
-
뜻은 단어장 설정의 모국어를 따라간다. 그 말이 사전에 없으면 영어로 물러서고, 영어도 없으면 빈 칸으로 둔다. 지어내 채우면 학습자가 그 틀린 뜻을 외운다 — 빈 칸은 채우면 되지만 틀린 뜻은 외우고 나서야 안다.
-
활용형을
alt로 함께 적어 둔다. 「먹었습니다」를 눌러도 「먹다」로 담긴다. 활용형이 그대로 쌓이면 단어장이 같은 말로 열 줄이 된다. -
alt로 다 못 적는다. 사전은 「먹다」로 적혀 있는데 지문은 언제나 「먹었습니다」다.gloss-find.js가 토씨와 어미를 떼어 사전에 있는 꼴로 되돌린다. 뗀 결과가 사전에 실제로 있을 때만 인정하고, 덜 뗀 쪽이 이긴다 — 「가지고」는 「가지다」에서 멈추지 「가다」까지 가지 않는다. 화면과 검사기가 이 파일 하나를 같이 쓴다. -
only: true는 「사전이 이 낱말에 단 뜻은 딴말이다」는 표시다. 사전의 「이」는 louse(이[蟲])고 「씨」는 seed(씨앗)다. 우리 지문에서는 「이 상자」의 이, 「민수 씨」의 씨다. 영어 표는 우리 것이 이기지만 이 표시가 없으면 언어팩에서 「しらみ」가 그대로 나간다. -
담기 전에 결과 화면에서 뜻을 고칠 수 있다. 손으로 적은 뜻은 낱말을 다듬어도 안 지워진다 — 지워지면 다듬는 일 자체를 안 하게 된다.
-
지금 얼마나 덮는지는
node tools/check-glossary.mjs가 말해 준다. 가짓수보다 나온 횟수로 센 쪽을 보면 된다. 학습자가 마주치는 것은 자주 나오는 낱말이다. 지금은 가짓수 75% · 횟수 90% 다 (2026-09). TOPIK II 읽기 문항을 600개 넘게 들이면서 57% · 77% 까지 떨어졌다가, 표제어를 2천 개 남짓 더해 되찾았다.build-glossary.mjs를 먼저 돌려야 한다 — 검사기가 구운glossary.js를 읽는다.한때 이 숫자가 99% 였다. 앞머리 한 글자만 맞아도 닿은 것으로 세고 있었고, 화면은 그때 정확히 일치하는 것만 찾아서 실제로는 47% 였다. 지금은 검사기가 화면과 같은
gloss-find.js를 불러 쓴다. 재는 쪽과 하는 쪽이 갈라지면 숫자가 일을 안 하고 위로를 한다.
다국어 뜻풀이는 국립국어원 「한국어기초사전」에서 가져온다. CC BY-SA 2.0 KR
이라 상업적으로 써도 되지만 출처를 밝혀야 하고, 거기서 나온 자료는 같은
라이선스로 열어 두어야 한다. 그래서 우리가 쓴 뜻풀이와 파일부터 갈라 둔다 —
한 파일에 섞으면 어디까지가 CC BY-SA 인지 말할 수 없게 된다.
자세한 것은 docs/plans/glossary-license.md.
내려받은 자료는 1GB 가까이 된다. 그대로 저장소에 넣지 말고
tools/build-krdict-glossary.mjs 로 우리 지문에 나오는 낱말만 뽑는다.
node tools/build-krdict-glossary.mjs ~/Downloads/krdict영어 뜻풀이를 손으로 더 채우려면 docs/antigravity/glossary-gemini-prompt.md 를 따른다.
글은 blog.js 에 있고 blog/ 밑은 생성물이다. 배열 순서가 화면 순서다 —
최신이 앞이고, 앞뒤 글 이동도 이 순서를 따른다.
예전에는 body 에 HTML 문자열을 넣었다. 손으로 쓸 때는 됐는데 모델에게
글을 받기 시작하니 안 됐다. 태그 하나가 안 닫히면 쪽 전체가 무너지고,
무엇보다 모델이 문법 뜻풀이를 그럴듯하게 지어냈다 — 우리에게는 손으로
다듬은 뜻풀이가 이미 290개 있는데.
그래서 본문을 블록 배열로 받는다.
blocks: [
{ t: 'p', text: '문단. 꾸밈은 **굵게** 하나뿐이다' },
{ t: 'h', text: '소제목' },
{ t: 'quote', lines: ['예문 한 줄', '또 한 줄'] },
{ t: 'list', items: ['…', '…'], ordered: false },
{ t: 'ex', ko: '늦어서 죄송합니다.', en: 'Sorry for being late.' },
{ t: 'dlg', lines: ['A: …', 'B: …'] },
{ t: 'gram', id: '32-2', note: '왜 지금 이걸 보라는지' },
{ t: 'link', href: '/compare/32.html', title: '…', note: '…' },
{ t: 'note', title: '자주 하는 실수', text: '…' },
{ t: 'img', src: '/assets/blog/….jpg', alt: '무엇이 찍혀 있는지', cap: '…' },
]글자는 전부 esc() 를 지난다. 모델이 <script> 를 적어 보내도 글자로만
남는다. 손으로 쓴 예전 글의 body 도 그대로 받는다 — 둘 다 굽는다.
gram 은 id 만 받는다. 표현 이름과 뜻풀이는 sentences.js 에서 꺼내
붙이고, note 에는 「왜 지금 보라는지」만 적는다. 모델은 어느 표현을 걸지만
정하고, 그 표현이 무슨 뜻인지는 우리 자료가 말한다. 없는 id 면
build-pages.mjs 가 굽다가 멈춘다 — 조용히 넘기면 글에 죽은 링크가 실린다.
첫 쪽(index.html)은 data-en 을 단 325군데를 단추 하나로 뒤집는다.
블로그 글에는 그렇게 안 한다. 두 가지 이유다.
하나, 구글은 한 주소에 한 말을 기대한다. 같은 주소에서 본문을 바꿔치기하면 크롤러가 보는 것은 둘 중 하나뿐이라, 영어 본문은 색인이 안 된다. 영어로 쓰는 목적이 영어 검색에 걸리는 것인데 목적을 잃는다.
둘, 한국어 글 17편 가운데 7편은 아직 영어 짝이 없다. 단추만 달면 단추는 English 라고 하는데 본문은 한국어인 쪽이 된다. 그게 아무것도 없는 것보다 나쁘다.
대신 말마다 글을 따로 쓰고 alt 로 묶는다.
{ id: 'eun-neun-vs-i-ga', alt: 'eun-neun-vs-i-ga-english' }
{ id: 'eun-neun-vs-i-ga-english', alt: 'eun-neun-vs-i-ga', lang: 'en' }묶으면 hreflang 이 양쪽에 붙고(서로를 가리켜야 구글이 인정한다),
글 머리에 「Read this in English →」 / 「한국어로 읽기 →」 줄이 뜬다.
x-default 는 영어 쪽이다 — 이 사이트의 기본이 영어라서(app.js 의
initLang), 말을 안 밝힌 방문자에게는 영어 쪽이 맞다.
한쪽만 걸면 check-blog.mjs 가 막는다. 한쪽만 걸린 hreflang 은
구글이 통째로 무시하는데, 화면에서는 전환 줄이 한쪽에만 떠서 눌러 보기
전에는 모른다.
갈래 쪽도 그 갈래의 글이 전부 영어면 쪽 전체가 영어로 나간다
(/blog/tag/english.html). 영어 글에서 「Blog」를 누르면 거기로 간다 —
한국어 목록으로 보내면 껍데기를 옮긴 뜻이 없어진다.
「how to learn Korean」처럼 영어로 찾는 사람에게 닿으려면 글 자체가 영어여야 한다. 한국어 글은 아무리 잘 써도 영어 검색어에 안 걸린다.
node tools/blog-prompt.mjs --en beginner "how to learn Korean" > /tmp/p.txt글에 lang: 'en' 이 실리면 그 쪽만 <html lang="en"> 으로 나가고,
빵부스러기·단추·앞뒤 글·댓글 상자까지 영어로 바뀐다. 껍데기를 한국어로
두면 반만 옮긴 것이다 — 영어로 읽어 내려온 사람 앞에 한국어 단추가
서면 자기 자리가 아닌 줄 알고 나간다.
gram 카드의 뜻풀이는 docs/grammar-en.json 에서 자동으로 붙으므로
영어 글에서도 지어낼 자리가 없다. 갈래는 English 로 따로 둔다 — 한국어
갈래에 섞으면 「같은 갈래의 글」이 영어 글 아래에 한국어 글을 건다.
node tools/blog-prompt.mjs beginner "이유를 말하는 세 가지" > /tmp/p.txt
# /tmp/p.txt 를 통째로 Gemini 에 붙여 넣고, 받은 JSON 을 /tmp/got.json 로 저장
node tools/blog-merge.mjs /tmp/got.json # --dry 를 붙이면 찍어만 본다
node tools/check-blog.mjs
node tools/build-pages.mjs && node tools/stamp.mjsblog-prompt.mjs 는 그 급수까지의 표현 id 목록과 걸어도 되는 주소 목록을
지시문 안에 통째로 넣는다. 말로 「있는 것만 걸어라」라고 해 봐야 모델은
/blog/tags/grammar 같은 있을 법한 주소를 아주 잘 지어낸다. 목록을 주면
지어낼 자리가 없다.
한 판에 글 하나만 시킨다. 두세 편을 한 번에 시키면 뒤로 갈수록 짧아지고 말투가 흔들린다 — 읽기 지문에서 이미 겪은 일이다.
blog-merge.mjs 는 막을 것만 본다(id 겹침·홑따옴표·모르는 블록). 나머지는
넣은 뒤 check-blog.mjs 가 한자리에서 본다 — 두 군데서 같은 것을 보면
한쪽만 고치게 된다.
assets/blog/ 에 둔다. 모델에게 사진을 시키지 않는다 — 남의 사진을 쓸
수 없기 때문이다. 지시문은 사진이 있으면 좋겠는 자리에 「사진 자리」라는
note 를 남기게 하고, 사람이 보고 채우거나 지운다. 크기·출처·alt 규칙은
assets/blog/00-README.txt 에 있다.
check-blog.mjs 가 파일이 실제로 있는지, alt 가 있는지(「사진」 같은 건
alt 가 아니다), 300KB 를 넘는지 본다.
목록과 글 머리에 「4일 전」으로 뜨는데, 구울 때가 아니라 보는 사람 브라우저에서 바꾼다. 정적 파일이라 구울 때 계산해 박으면 다음 날부터 거짓이 된다. 스크립트가 안 돌면 진짜 날짜가 그대로 남는다.
extension/ 에 있다. 사이트의 사전·문법·표현을 읽던 쪽 위로 가져가는
물건이다 — 한국어 기사를 읽다가 막혔을 때 사이트로 건너오지 않고 그
자리에서 뜻을 본다.
자세한 것은 extension/README.md. 여기서는 저장소를 만지는 사람이 알아야
할 것만 적는다.
extension/data/ 는 저장소에 없다. 굽고 써야 한다.
node tools/build-extension.mjs # 사이트 자료 → extension/data/ (2.6MB)
node tools/check-extension.mjs # 꾸릴 수 있는 꼴인지 (자료가 낡았는지도 함께 본다)다른 생성물과 달리 저장소에 안 넣는 까닭은, 이것이 GitHub Pages 가 내주는 것이 아니라 익스텐션을 꾸릴 때만 쓰는 사본이어서다. 넣어 두면 같은 사전 2.6MB 가 저장소에 두 벌이 되고, 두 벌이 되면 어느 쪽이 진짜인지 묻게 된다.
glossary.js · sentences*.js · grammar.js 를 고쳤으면 다시 구울 것.
안 구우면 사이트와 익스텐션이 다른 뜻을 내준다 — 그것도 틀린 줄 모르는
채로. 익스텐션은 멀쩡히 돌아가고 예전 뜻을 정확하게 보여 준다. stamp.mjs
를 빼먹었을 때와 같은 종류의 사고다.
익스텐션은 찾는 규칙을 새로 짜지 않는다. gloss-find.js 와
grammar-find.js 를 그대로 가져다 쓴다 — 사이트에서 「먹었습니다」가
「먹다」로 담기면 익스텐션에서도 그렇게 담긴다. 재는 쪽과 하는 쪽이
갈라지면 숫자가 일을 안 하고 위로를 한다는 것을, 뜻풀이 덮는 비율에서 한
번 겪었다.
낱말을 모르면 눌러서 사전을 보는데 어미는 눌러 볼 데가 없었다. 「-는 바람에」를 처음 본 사람은 그것이 한 덩어리인 줄도 모르고, 「바람」을 사전에서 찾다가 「wind」를 보고 더 헷갈린다.
예문 만들기에 그 설명이 290개나 쌓여 있는데 읽는 사람이 거기로 갈 길이
없었다. 그래서 읽기 연습 지문에서 아는 문법에 밑줄을 긋고, 누르면 말풍선으로
무엇인지 띄우고, 거기 단추로 그 문법 쪽(#learn/sentence/51-4)으로 바로
건너가게 했다.
node tools/build-grammar.mjs # grammar.js 를 굽는다
node tools/build-grammar.mjs --report # 지문 어디에 걸리는지 전부 본다문법 이름(sentences.js 의 name)은 사람이 읽으라고 적힌 것이라
(「A/V-(으)ㄴ/는데 ①」) 그대로는 글에서 못 찾는다. build-grammar.mjs 가 그것을
찾을 수 있는 꼴로 옮긴다 — (으)ㄴ 은 받침으로 붙거나 「은」이 되고, 「-았/었」은
녹아 붙어도 받침 ㅆ 을 남기고(갔·먹었·했·봤), 「-아/어」는 종잡을 수 없어
받침 없고 홀소리가 ㅏㅐㅓㅕㅘㅙㅝ인 글자로 좁혀 둔다.
안 짚는 것이 셋 있다. 셋 다 지문 54편에 실제로 대 보고 정했다.
- 이름이 꼴이 아닌 것 — 「'ㅂ' 불규칙」 「직접 인용」 「하오체」. 글에서 찾을 자국이 없다.
- 글자만 보고 못 가르는 것 —
BLIND. 「-(으)ㄴ들」은 「만들」 「현대인들」에, 「-았/었다가」는 「게다가」에, 「-아/어 보다」는 「비판보다」에 걸렸다. 걸린 자리를 그대로 적어 두었으니, 되살리고 싶으면--report로 대 보고 되살려라. - 너무 자주 나오는 것 —
TOO_BASIC. 「-습니다」는 54편에서 274번 걸린다. 문장마다 하나씩이다. 다 밑줄을 그으면 글이 아니라 밑줄이 된다. 자리 토씨 (이/가·은/는·을/를·의)도 같다.
토씨를 한때 통째로 뺐다가 되돌렸다 — 초급 지문 열여덟 편 가운데 여섯 편에 밑줄이 하나도 안 남았기 때문이다. 초급 글이 바로 그 토씨로 쓰여 있다. 「에서」와 「에」의 차이, 「에게」, 「보다」는 초급이 실제로 헷갈리는 것이라 남겼다. 지금은 한 편에 4.0군데, 밑줄이 하나도 없는 지문은 54편 중 2편이다.
sentences.js 의 설명은 한국어뿐이다. 한국어를 배우러 온 사람에게 한국어로
설명하면 설명이 또 하나의 숙제가 된다. 영어는 sentences.js 에 섞지 않고
docs/grammar-en.json 에 따로 두고 id 로 맞춰 붙인다 — 그 파일은 손으로 오래
다듬은 자료라, 번역을 한 줄씩 끼워 넣다가 다른 줄을 건드리면 되돌릴 데가 없다.
옮기는 칸은 셋이다. desc(뜻·쓰임) · form(형태) · care(주의할 점).
예문과 「자주 함께 쓰는 말」은 한국어 그대로 둔다 — 옮길 것이 아니라 배울
것이다.
node tools/grammar-words.mjs 40 > /tmp/batch.txt # 아직 안 옮긴 것 40개
# docs/antigravity/grammar-gemini-prompt.md 뒤에 붙여 보내고, 받은 JSON 을 붙인다
node tools/build-grammar.mjs && node tools/stamp.mjsbuild-grammar.mjs 가 빠진 칸·겹친 id·낫표 밖에 남은 한글을 짚어 준다
(실제로 이 검사가 「켜다↔끄다」를 잡았다). 채운 것은 다음 묶음에서 저절로
빠지므로 어디까지 했는지 따로 적어 둘 것이 없다.
안 옮긴 것은 한국어로 물러선다. 지어내 채우면 배우는 사람이 그 틀린 설명을 그대로 외운다. 지금은 290개 전부 채워져 있다 — 말풍선도, 예문 만들기 상세도 영어 화면에서는 영어로 나간다.
구글은 사이트맵의 changefreq 와 priority 를 안 읽는다(공식 문서에
그렇게 적혀 있다). 읽는 것은 lastmod 하나다 — 어디를 다시 기어올지
고르는 데 쓴다. 993줄에 안 읽는 것 둘만 적혀 있고 읽는 것은 비어 있었다.
다만 정확할 때만 읽는다. 처음에는 원본 파일의 git 커밋 날을 쓰려 했는데
그게 안 됐다. stamp.mjs 가 sentences.js 안의 ?v= 를 다시 찍으면 그
파일의 커밋 날이 오늘로 뛴다 — 표현 290쪽의 내용은 한 글자도 안 바뀌었는데
사이트맵은 「오늘 290쪽이 바뀌었다」고 말하게 된다. 그런 사이트맵을 몇 번
보고 나면 구글은 그 사이트의 lastmod 를 통째로 안 믿는다.
그래서 구운 쪽 자체의 바이트를 해시해서 지난번과 다를 때만 날짜를
올린다. 기록은 docs/page-mod.json 이다. 정적 쪽에는 자국이 안 들어가므로
(CSS 를 박아 넣는다) 자국을 다시 찍어도 해시가 안 흔들린다.
확인해 본 것:
- 자국만 다시 찍음 → 모든 쪽 날짜 그대로
- 글 하나의 요약을 한 낱말 고침 → 그 글과 그 요약을 싣는 3쪽만 오늘로
(글 쪽·
/blog/목록·「같은 갈래의 글」로 그 카드를 싣는 글들)
docs/page-mod.json 을 지우면 전부 오늘로 다시 잡힌다. 틀린 날짜가 되는
것은 아니고 그 전을 모르게 될 뿐이다.
한 페이지 안에서 화면을 갈아 끼우고, 주소(해시)에 어디인지 남긴다. 새로고침해도 그 자리에 있어야 하기 때문이다.
# 홈
#wordbook #account #library #dashboard #test
#games #quiz #num
#learn 배우기 (갈래 고르기)
#learn/topik 배우기 › TOPIK 유형 연습
#learn/courses 배우기 › 코스로 배우기
#learn/reading 배우기 › 읽기 연습
#learn/sentence 배우기 › 예문 만들기
#learn/sentence/23-1 그 안의 표현 하나
화면을 새로 만들면 주소도 같이 준다. app.js 의 VIEW_SLUG 에 한 줄,
app.module.js 의 open() 에 한 줄이다. 안 주면 새로고침했을 때 홈으로
튕긴다.
모의고사를 푸는 중에는 화면을 떠나기 전에 cpBlockLeave 가 묻는다.
70분짜리가 뒤로 가기 한 번에 사라지지 않게 하려는 것이다.
배우기 갈래를 처음 열었을 때 한 번만 뜬다. 글은 app.module.js 의
GUIDES 에 갈래별로 한국어·영어 두 벌이 있다.
들어올 때마다 띄우지 않는 이유 — 세 번째 방문쯤이면 읽지 않고 닫는 단추가
되고, 그러면 정작 처음 온 사람도 그렇게 배운다. 대신 갈래 제목 옆 ? 로
언제든 다시 연다.
- 걸음은 셋까지. 넷을 넘으면 첫 화면에 스크롤이 생기는데, 안내를
스크롤해 가며 읽는 사람은 없다. 걸음이 아닌 경고는
warn에 따로 적는다. - 남이 보낸 주소로 표현 하나를 콕 집어 들어왔을 때는 안 뜬다
(
openSection(id, quiet)). 그 표현을 보러 온 사람 앞을 안내가 가로막으면 안내가 아니라 문지기가 된다. - 「봤다」는 닫을 때 적는다. 열자마자 적으면 띄워 놓고 새로고침한 사람이 안내를 영영 못 본다.
- 갈래를 새로 만들면
GUIDES에 한 줄 넣는다. 없으면?가 숨고 안내도 안 뜬다 — 조용히 넘어가므로 깨지지는 않는다.
레벨은 8단계다 — app.module.js 의 LT_LEVELS.
| 레벨 | 이름 | 코스 | TOPIK |
|---|---|---|---|
| L0 | 한글 전 | hangul | — |
| L1 | 입문 | first-words · grammar-core | — |
| L2 | 초급 1 | Stage 1 (courses-beginner-stage1.js) + bg-d-01 |
1급 |
| L3 | 초급 2 | Stage 2 + bg-d-02~05 | 1급 |
| L4 | 초급 3 | Stage 3–4 | 2급 |
| L5 | 초급 4 | Stage 5–6 + bg-04 · bg-35 · bg-36 | 2급 |
| L6 | 중급 | level: 'Intermediate' 코스 전부 (중급 30개) |
3–4급 |
| L7 | 고급 | level: 'Advanced' 코스 전부 (고급 24개) |
5–6급 |
초급은 레벨별 코스 목록을 MY_LEVEL_COURSES 에 손으로 적는다. 초급 코스를 새로
더하면 거기에도 넣을 것 — 안 넣으면 「내 코스」의 길에 안 나온다. 중·고급은
level 로 저절로 들어간다.
레벨테스트는 목적마다 다 계단식이다. 「전체」·「쓰기」는 우리 레벨(L0L7) 사다리를,
TOPIK·읽기·듣기는 TOPIK 문항 은행(문항마다 6급) 사다리를 오르내리고,
급수 결과는 grade)의 급수(1LT_GRADE_TO_LEVEL 로 우리 레벨에 옮긴다. 「내 코스」 레벨은 「전체」 결과로
정하고, 다른 목적은 레벨이 아직 없을 때만 채운다.
「전체」 는 L3 에서 시작해 맞히면 한 레벨 위, 틀리면 한 레벨
아래에서 다음 문제를 낸다(최대 10문제, 경계를 찾으면 일찍 끝). 맞힌 수가 틀린 수보다
많은 레벨 중 가장 높은 것의 바로 위가 공부할 레벨이다. 문제는
leveltest-overall.js 에 레벨마다 8개 — 한 레벨을 두세 번 물어야 경계가 서므로
레벨마다 8개 밑으로 떨어뜨리지 않는다. 정답이 둘인 문제는 판정을 흔든다 — 오답
보기도 말이 되는 문장이면 그 문제는 버린다.
「내 코스」 는 배우기 첫 화면 맨 위에 선다. 이 기기에 남기는 것 셋:
| 열쇠 | 무엇 |
|---|---|
cp_level |
레벨 · 목표 · 하루 시간 (레벨테스트 결과) |
cp_done_local |
끝낸 레슨 — 로그인 안 해도 길이 이어지게. 불러올 때 서버 진도와 합친다 |
cp_weak |
빈칸을 틀린 레슨 최근 12개 — 다시 풀어 하나도 안 틀리면 빠진다 |
중·고급 코스 계획은 docs/plans/curriculum-upper.md — 예문 만들기 갈래 하나가 코스 하나다
(중급 26 · 고급 22, 전부 채움). 새 코스는 course-prompt.mjs → course-merge.mjs →
check-courses.mjs 순서로 넣는다(docs/antigravity/antigravity-level-content-task.md).