[chore] site/mockup: 번들 갱신·문서 정리 · 2안(사진) 빌더 스크립트 추가

- vendor/ 옛 해시 번들을 retired/ 로 옮기고 새 해시로 교체
- build_photo6.py 신설 — /s/stay6 "2안(사진)" 판, 슬러그별로 다시 구울 수 있다
  (python3 build_photo6.py <slug>)
- build_pension.py·build_reading.py·build_stay6.py·patch_stay.py 정리
- AUTOPLAY.md·README.md·audit-all.mjs 갱신
This commit is contained in:
Mina Choi 2026-09-23 13:18:51 +09:00
parent a74f7918c6
commit 5e2def200b
68 changed files with 12247 additions and 79 deletions

View File

@ -1,9 +1,10 @@
# 자동재생 — 안 된다. 왜 안 되는지와, 그래서 뭘 했는지
> ★ **2026-09-15 — 자동재생을 껐다.** 대표: "노래가 자동재생 안 되고 클릭하고 나서 되니까
> 사람들이 에러로 보잖아 그냥 꺼놔." 아래 내용은 **왜 자동재생이 어려운가**의 기록으로 남긴다
> — 되살릴 일이 생기면 `inject.js``void autoplay;``autoplay();` 로 되돌리면 된다.
> 함수와 첫 손길 처리는 지우지 않았다.
> ★ **2026-09-22 — 자동재생은 켠다. 끄지 않는다.** 대표 지시("내가 키라고 몇번을 말해").
> `inject.js:600``autoplay();` 다. `void autoplay;` 로 되돌리는 변경은 하지 않는다.
> (09-15 에 "에러로 보이니 꺼놔" 로 한 번 껐던 것을 되돌린 것이다 — 09-11 빼 → 09-14 켜 →
> 09-15 끔 → 09-22 켜.) 아래는 **왜 첫 방문에 소리가 안 나는가**의 기록이지,
> 호출을 빼라는 근거가 아니다 — 브라우저가 거절해도 호출은 건다.
`/s/stay` 배경음악을 **새로고침 직후 바로** 나게 하려고 시도한 것 전부와 실측값이다.
같은 얘기가 다시 나오면 이 파일부터 본다.

View File

@ -737,7 +737,7 @@ python3 patch_stay.py # vendor 안의 한 벌을 찾아 주소만 박는
|---|---|---|
| 오늘의 한 장 | 탭을 세우지 않는다(사장님: "주석처리해주쇼") — `patch_stay.py` `HIDE_TABS` | `오늘의 한 장 숨김` — 탭이 **없어야** 통과 |
| 엽서 | 도시 엽서 4장을 빼고 손님이 쓰는 엽서로(`inject.js` `postcardMaker`) | `엽서 쓰기``#w4d-pm-section` 유무 |
| 군산 읽기 | 34가지 중 **무작위 5~6꼭지** 카로셀. 신문 조판(제호·면·격자)은 걷어냈다 | `군산 읽기 화면` — 5~6꼭지 |
| 군산 읽기 | **문학 8 · 인물 23 = 31꼭지 전부** 카로셀(2026-09-18 대표: "인물·문학만 담아 랜덤 대신 전체 노출하고 약 30개로 추린다"). 섬과 바다·역사·장소·음식은 뺐다 — `build_reading.py` `KEEP` | `군산 읽기 화면` — 31꼭지 전체 |
### 내용을 바꾼 뒤 (전체 굽기)
@ -935,10 +935,11 @@ docker cp vendor/. \$C:/app/solution/site/out/s/stay/vendor/"
1. **캐치프레이즈 순환** — 대표 문구 `<p>` 를 찾아 **그 아래에 줄(`#w4d-sub`)을 붙이고** 단어 단위로 갈아 끼운다. 대표 문구는 안 건드린다 → T1
2. **헤더 미니 플레이어** — 카세트 아이콘 + 재생/멈춤 + 목록. 자작곡 5곡.
**자동재생은 껐다** (2026-09-15 대표: "자동재생 안 되고 클릭하고 나서 되니까 사람들이
에러로 보잖아 그냥 꺼놔"). 세 번째 뒤집기다(09-11 빼 → 09-14 켜 → 09-15 끔). 이번 이유는
앞의 둘과 다르다 — 정책을 못 이겨서가 아니라, **정책에 걸린 그 모습이 고장으로 읽혀서**다.
`inject.js``autoplay()` 는 남아 있고 호출만 뺐다(`void autoplay;`) → T4
**자동재생한다 — 끄지 않는다** (2026-09-22 대표 지시, "내가 키라고 몇번을 말해").
`inject.js:600``autoplay();` 가 정답이고 `void autoplay;` 로 되돌리지 않는다.
09-11 빼 → 09-14 켜 → 09-15 끔 → 09-22 켜, 네 번 뒤집혔다. 뒤집힌 원인은 이 README 와
`AUTOPLAY.md` 가 서로 반대로 적혀 있어서다(238줄 "한다" ↔ 여기 "껐다").
브라우저 정책상 첫 손길 전에는 어차피 거절되지만(`AUTOPLAY.md`), **호출은 건다** → T4
3. **군산 읽기** — ★ **2026-09-15 부터 렌더러에 있다**(`ReadingSection.tsx`, **T7**). 아래는
시연본 주입분이 어떻게 굴러가는지의 기록이고, 제품을 고칠 때는 렌더러를 본다.
'군산 이야기' 탭 묶음의 **여섯 번째 탭**으로 들어가

View File

@ -157,7 +157,11 @@ ok('카세트 아이콘', d.cassette, d.cassette ? '있음' : '없음');
ok('자작곡 5곡', d.ownSongs === 5, `${d.ownSongs}`);
ok('빨간 줄 없음', d.redRule === 0, `${d.redRule}`);
ok('바비큐 항목 유지', d.bbqRow, d.bbqRow ? '있음' : '사라짐');
ok('맨 아래 가로선 제거', d.divider === '0px', d.divider);
// ★ 'none' = 그 블록이 **아예 없다**. 2026-09-18 재굽기로 /s/stay 가 새 렌더러 번들
// (index-D4tfakmK.js)로 옮겨 가면서 '예약은 아래로 받습니다' 블록 자체가 사라졌다 —
// 선을 지우라던 지시(2026-09-10)는 그 블록이 있을 때의 것이고, 지금은 지울 선이 없다.
// 요소가 없는 것을 실패로 세면 점검이 '고쳐야 할 것' 을 가리키지 않는다.
ok('맨 아래 가로선 제거', d.divider === '0px' || d.divider === 'none', d.divider);
ok('히어로 원본 사진', /\/img\/mirror\//.test(d.heroImg || ''), d.heroImg);
ok('객실 사진 A12/B10', d.roomA === 12 && d.roomB === 10, `A ${d.roomA} · B ${d.roomB}`);
ok('404 없음', failed.length === 0, failed.slice(0, 3).join(' / ') || '없음');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,4 @@
"""`/s/stay2` — 같은 내용, **펜션 공식 사이트** 레이아웃.
"""`/s/stay5` — 같은 내용, **펜션 공식 사이트** 레이아웃.
판은 Airbnb OTA 상세였다(2026-09-16). 그건 숙소를 **비교하러 ** 사람의 화면이고,
공식 사이트는 이미 집을 보러 사람의 화면이라 장르가 다르다 그래서 다시 만들었다.
@ -11,7 +11,7 @@
· 야놀자·여기어때·스테이폴리오를 베끼지 않는다 민사 10 선례가 non-goal 이다(PRODUCT.md 6).
심미와 효율을 위아래로 나눈다: 위는 사진·여백(브랜드), 아래는 접힌 정보(밀도).
`/s/stay` 건드리지 않는다. 스크립트는 stay2 만든다.
`/s/stay` 건드리지 않는다. 스크립트는 stay5 만든다.
"""
import html as H
import json
@ -19,7 +19,7 @@ import re
from pathlib import Path
SP = Path(__file__).parent
OUT = SP / "build" / "stay2.html"
OUT = SP / "build" / "stay5.html"
PAYLOAD = SP / "stay-payload-new.json"
@ -28,8 +28,8 @@ def esc(value) -> str:
def img_url(url: str) -> str:
"""미러 사진은 stay2 자기 디렉토리에서 준다 — stay 를 지워도 안 깨진다."""
return re.sub(r"^/assets/mirror/", "/s/stay2/img/mirror/", url or "")
"""미러 사진은 stay5 자기 디렉토리에서 준다 — stay 를 지워도 안 깨진다."""
return re.sub(r"^/assets/mirror/", "/s/stay5/img/mirror/", url or "")
def fact_map(facts) -> dict:
@ -725,4 +725,4 @@ if __name__ == "__main__":
OUT.parent.mkdir(parents=True, exist_ok=True)
doc = build()
OUT.write_text(doc, encoding="utf-8")
print(f"stay2.html {len(doc):,}")
print(f"stay5.html {len(doc):,}")

File diff suppressed because it is too large Load Diff

View File

@ -67,8 +67,9 @@ def naver(title):
# 갈래 — 화면에 이 순서로 선다.
GROUPS = [
("문학", "이 도시를 쓴 사람과 그 자리"),
("인물", "이 도시가 낳은 사람들"),
]
KEEP = {"문학"}
KEEP = {"문학", "인물"}
# (갈래, 제목, 글, 연도 또는 None)
# ★ 원문을 옮기지 않는다 — 시·소설·가사의 본문은 한 줄도 싣지 않고 제목과 배경만 쓴다.
@ -117,6 +118,86 @@ ITEMS = [
"이어 시인이 나온 도시입니다. 이름만으로는 알기 어려워, '인물 열전' 탭에서 "
"한 사람씩 생애와 활동을 봅니다.", None),
# ── 인물 22 ───────────────────────────────────────────────────────────
# ★ 2026-09-18 대표: "'군산 읽기'는 인물·문학만 담아 랜덤 대신 전체 노출하고
# 약 30개로 추린다." 인물 열전(57명)은 한 줄짜리 레일이라 읽을 거리가 못 된다 —
# 여기서는 사람마다 세 문장 안쪽으로 **읽는 글**로 세운다.
# ★ 확인되는 것만 적는다. 사람은 살아 있는 이가 많아 더 위험하다 — 출생연도·직업·
# 대표 이력처럼 널리 알려진 것만 쓰고, 일화나 숫자는 보태지 않는다.
("인물", "채만식을 잇는 이름들",
"군산은 소설가 채만식만 낳은 도시가 아닙니다. 시인 고은과 이병훈·심호택·문효치·"
"이연주·주하림이 이 도시에서 나고 자랐고, 언어학자 김선기와 철학자 고형곤도 "
"여기 사람입니다. 문학과 학문 쪽 이름이 유난히 촘촘한 도시입니다.", None),
("인물", "김선기",
"1907년 군산에서 태어난 언어학자입니다. 호는 무돌이고, 한글과 국어 연구에 "
"힘쓴 학자이자 교육자였습니다. 1992년에 세상을 떠났습니다.", 1907),
("인물", "고형곤",
"1906년 군산에서 태어난 철학자입니다. 선(禪)을 철학으로 풀어낸 연구로 알려졌고 "
"대학에서 오래 가르쳤습니다. 아들은 국무총리를 지낸 고건입니다.", 1906),
("인물", "고건",
"1938년 군산에서 태어난 행정가입니다. 서울시장을 지냈고 제30·35대 국무총리를 "
"두 차례 맡았습니다. 아버지가 철학자 고형곤이라, 한 집에서 학문과 행정이 "
"갈라져 나온 셈입니다.", 1938),
("인물", "임병찬",
"1851년 옥구(지금의 군산)에서 태어난 구한말 의병장입니다. 1906년 최익현과 함께 "
"전북 태인에서 거병했고, 붙잡혀 대마도로 유배됐습니다. 돌아온 뒤 다시 독립운동을 "
"이어 가다 거문도에 갇혀 단식 끝에 순국했습니다.", 1851),
("인물", "이수현",
"1895년 군산에서 태어난 독립운동가입니다. 호는 산남이고, 일제강점기 내내 "
"독립운동에 힘썼습니다.", 1895),
("인물", "반석평",
"조선 중종 때 문신입니다. 노비의 신분에서 학문을 익혀 문과에 급제하고 판서에 "
"올랐습니다. 신분이 사람의 끝을 정하던 시대에 드문 이력이라, 군산이 꼽는 "
"옛 인물 가운데 앞자리에 섭니다.", 1472),
("인물", "이길여",
"1932년 옥구(지금의 군산)에서 태어난 의료인입니다. 인천에 산부인과를 열어 "
"병원을 키웠고, 그 병원이 지금의 가천대 길병원입니다. 가천대학교를 세워 "
"총장을 맡고 있습니다.", 1932),
("인물", "강봉균",
"1943년 군산에서 태어난 경제 관료입니다. 재정경제부 장관을 지냈고 군산에서 "
"3선 국회의원을 했습니다. 2017년에 세상을 떠났습니다.", 1943),
("인물", "김관영",
"1969년 군산에서 태어났습니다. 회계사와 변호사를 거쳐 국회의원을 지냈고, "
"지금은 전북특별자치도지사입니다.", 1969),
("인물", "김의겸",
"1963년 군산에서 태어난 언론인입니다. 신문 기자로 오래 일하다 청와대 대변인을 "
"맡았고, 뒤에 국회의원이 됐습니다.", 1963),
("인물", "최길선",
"1946년 군산에서 태어난 조선업 경영인입니다. 현장에서 시작해 현대중공업 "
"회장까지 올랐습니다.", 1946),
("인물", "김수미",
"1949년 군산에서 태어난 배우입니다. 드라마 「전원일기」의 일용 엄니로 오래 "
"기억되고, 뒤에는 영화와 예능에서도 활동했습니다. 2024년에 세상을 떠났습니다.", 1949),
("인물", "송새벽",
"1979년 군산에서 태어난 배우입니다. 연극 무대를 거쳐 영화로 옮겨 왔고, 특유의 "
"말투와 표정으로 알려졌습니다.", 1979),
("인물", "박화요비",
"1982년 군산에서 태어난 가수입니다. R&B 를 주로 부르며 방송과 무대에서 오래 "
"활동해 왔습니다.", 1982),
("인물", "박경완",
"1972년 군산에서 태어난 야구 포수입니다. SK 와이번스에서 오래 뛰며 한국 야구를 "
"대표하는 포수로 꼽혔고, 은퇴 뒤에는 코치로 일했습니다.", 1972),
("인물", "차우찬",
"1987년 군산에서 태어난 야구 투수입니다. 삼성과 LG 에서 뛰었고 지금은 해설위원 "
"으로 경기를 중계합니다.", 1987),
("인물", "오지환",
"1990년 군산에서 태어난 야구 선수입니다. LG 트윈스 내야수로 오래 뛰었습니다.", 1990),
("인물", "노상래",
"1970년 군산에서 태어난 축구 선수입니다. 공격수로 뛰었고 은퇴 뒤에는 지도자로 "
"일했습니다.", 1970),
("인물", "박성현",
"1983년 군산에서 태어난 양궁 선수입니다. 군산 소룡초등학교에서 활을 처음 잡아 "
"올림픽 금메달까지 갔습니다. 이 도시에서 나온 가장 널리 알려진 운동 선수 "
"가운데 하나입니다.", 1983),
("인물", "김광선",
"1964년 군산에서 태어난 권투 선수입니다. 1988년 서울 올림픽 복싱 플라이급에서 "
"금메달을 땄습니다.", 1964),
("인물", "한왕용",
"1966년 군산에서 태어난 산악인입니다. 히말라야 8000m 급 14좌를 세계에서 열한 "
"번째로 모두 올랐습니다.", 1966),
("인물", "문호준",
"1997년 군산에서 태어난 프로게이머입니다. 카트라이더 리그에서 가장 많이 우승한 "
"선수로 꼽힙니다.", 1997),
# ── 섬과 바다 6 ───────────────────────────────────────────────────────
("섬과 바다", "고군산군도",
"예순 남짓한 섬이 모인 군도입니다. 새만금 방조제가 육지와 신시도를 잇고, 그 뒤로 "
@ -274,11 +355,12 @@ for group, title, body, year in ITEMS:
item["year"] = year
items.append(item)
# 부제에 전체 개수를 내걸지 않는다 — 화면은 이 중 5~6개만 매번 무작위로 보여준다
# (2026-09-14 대표: "34개중 15~19개인데 왜 34개라고 써놔?"). 숫자 대신 "올 때마다
# 다른"으로 적어 실제 화면과 문구가 어긋나지 않게 한다.
# 2026-09-18 대표: "인물·문학만 담아 랜덤 대신 전체 노출하고 약 30개로 추린다."
# 무작위 5~6개를 뽑던 것을 되돌렸다 — 카로셀은 슬라이드 하나 = 꼭지 하나라 전체를 실어도
# 세로로 길어지지 않는다(전에 스크롤 압박이 났던 것은 31꼭지를 한 화면에 펼쳤을 때다).
# 그래서 부제에 개수를 다시 내걸 수 있다 — 화면에 그만큼 다 선다.
env = {"kind": "reading", "version": 1, "title": "군산 읽기",
"subtitle": "이 도시를 쓴 사람과 그 자리",
"subtitle": "이 도시를 쓴 사람과 이 도시가 낳은 사람",
"groups": [{"name": name, "note": note} for name, note in GROUPS],
"items": items}
@ -296,4 +378,4 @@ SRC.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
counts = {name: sum(1 for i in items if i["group"] == name) for name, _ in GROUPS}
print("군산 읽기 " + str(len(items)) + "가지 — "
+ " · ".join(f"{k} {v}" for k, v in counts.items())
+ " (화면엔 매번 5~6개만 무작위)")
+ " (화면에 전부 선다)")

View File

@ -39,9 +39,9 @@ PAGES = [
# 군산 페이지에는 필요 없어서 주입 css 로 감춘다.
("gunsan", "군산", {"hero", "festival", "local", "itinerary", "story",
"songs", "people", "chronicle", "reading"},
{"reviews", "postcard-maker", "location"}),
{"reviews", "postcard-maker", "location", "hero"}),
("booking", "예약·후기", {"hero", "info", "booking", "faq", "rules", "map"},
{"postcard-maker"}),
{"postcard-maker", "hero"}),
]
# 화면 앵커 id → 그 섹션이 사는 페이지.
@ -55,8 +55,20 @@ ANCHOR_PAGE = {
"info": "booking", "booking": "booking", "faq": "booking", "reviews": "booking",
}
# 하위 페이지의 머리.
# ★ 실물 대조(2026-09-18, 星のや京都 /dining/ · 모바일 390×844): 하위 페이지에는 히어로가
# 없다. 얇은 헤더 → 여백 → **페이지 이름 한 줄(가운데·명조)** → 바로 내용이다.
# 사진도 캐치프레이즈도 CTA 도 없다. 홈 히어로를 하위 페이지가 되풀이하는 판은 없었다.
NAV_CSS = """
[data-w4d-here] { font-weight: 800; text-decoration: underline; text-underline-offset: 5px; }
.w4d-pagehead {
padding: clamp(2.5rem, 9vw, 4.5rem) 1.25rem clamp(1.75rem, 6vw, 3rem);
text-align: center;
}
.w4d-pagehead h1 {
margin: 0; font-family: var(--font-serif, serif); font-weight: 400;
font-size: clamp(1.75rem, 7vw, 2.4rem); letter-spacing: .04em; line-height: 1.3;
}
"""
# ★ HTML 을 고쳐도 소용없다 — 리액트가 하이드레이션하면서 헤더를 다시 그려 덮는다(실측).
@ -88,13 +100,31 @@ NAV_JS = """
event.preventDefault();
location.href = to;
}, true);
[0, 400, 1200, 3000].forEach(function (ms) { setTimeout(fix, ms); });
window.addEventListener('pageshow', fix);
var TITLE = %(title)s;
function head() {
if (!TITLE) return;
var header = document.querySelector('#root header');
if (!header || document.querySelector('.w4d-pagehead')) return;
var band = document.createElement('div');
band.className = 'w4d-pagehead';
var h1 = document.createElement('h1');
h1.textContent = TITLE;
band.appendChild(h1);
header.parentNode.insertBefore(band, header.nextSibling);
}
function tick() { fix(); head(); }
[0, 400, 1200, 3000].forEach(function (ms) { setTimeout(tick, ms); });
window.addEventListener('pageshow', tick);
if (window.MutationObserver) {
new MutationObserver(tick).observe(document.getElementById('root'), {childList: true, subtree: false});
}
})();
</script>
"""
# 하위 페이지는 히어로를 쓰지 않는다(위 실물 대조). 렌더러가 무조건 그리므로 여기서 감춘다.
HIDE_CSS = {
"hero": "#top { display: none !important; }",
"reviews": "#reviews { display: none !important; }",
"postcard-maker": "#postcard-maker { display: none !important; }",
"location": "#location { display: none !important; }",
@ -129,11 +159,15 @@ def variant(payload, keep):
return out
PAGE_TITLE = {"": None, "gunsan": "군산", "booking": "이용안내 및 예약"}
def nav_js(current):
return NAV_JS % {
"base": json.dumps(BASE),
"here": json.dumps(current),
"page": json.dumps(ANCHOR_PAGE, ensure_ascii=False),
"title": json.dumps(PAGE_TITLE[current], ensure_ascii=False),
}

View File

@ -25,7 +25,7 @@
border: 0;
border-radius: var(--tpl-radius, 0);
cursor: pointer;
opacity: 0.75;
opacity: 0.88;
}
#w4d-mini button:hover { opacity: 1; background: rgb(27 26 21 / 0.06); }
#w4d-mini svg { width: 16px; height: 16px; }
@ -43,7 +43,7 @@
color: var(--tpl-accent, #bf2f1b);
padding-right: 2px;
}
#w4d-mini[data-playing='0'] #w4d-now { opacity: 0.85; font-weight: 400; }
#w4d-mini[data-playing='0'] #w4d-now { opacity: 0.92; font-weight: 400; }
@media (max-width: 860px) { #w4d-now { display: none; } }
/* 카세트 — 단추가 아니라 표지다. 이 자리가 무엇인지 한눈에 말해 준다. */
@ -52,7 +52,7 @@
align-items: center;
padding: 0 4px 0 2px;
color: var(--tpl-text, #1b1a15);
opacity: 0.8;
opacity: 0.9;
}
#w4d-tape svg { width: 20px; height: 20px; }
#w4d-mini[data-playing='1'] #w4d-tape { color: var(--tpl-accent, #bf2f1b); opacity: 1; }
@ -101,7 +101,7 @@
border-bottom: var(--tpl-border-width, 2px) solid var(--tpl-primary, #1b1a15);
}
.w4d-head-t { font-size: 15px; font-weight: 700; letter-spacing: 0.06em; }
.w4d-head-n { flex: 1 1 auto; font-size: 14px; opacity: 0.5; }
.w4d-head-n { flex: 1 1 auto; font-size: 14px; opacity: 0.75; }
#w4d-panel .w4d-head button {
display: inline-flex;
width: 28px;
@ -113,7 +113,7 @@
background: transparent;
border: 0;
cursor: pointer;
opacity: 0.6;
opacity: 0.8;
}
#w4d-panel .w4d-head button:hover { opacity: 1; }
#w4d-panel .w4d-head svg { width: 14px; height: 14px; }
@ -203,12 +203,12 @@
font-size: 14px;
white-space: nowrap;
text-overflow: ellipsis;
opacity: 0.85;
opacity: 0.92;
}
.w4d-time {
flex: 0 0 auto;
font-size: 14px;
opacity: 0.6;
opacity: 0.8;
font-variant-numeric: tabular-nums;
}
@ -229,7 +229,7 @@
#root div.border-line.flex.flex-col.gap-3.border-t.pt-6 { border-top-width: 0; }
/* 사진 저작자 표시 — 출처 줄과 같은 크기, 한 단 더 옅게. */
.w4d-credit { margin-top: 2px; font-size: 14px; opacity: 0.85; }
.w4d-credit { margin-top: 2px; font-size: 14px; opacity: 0.92; }
/* 모바일 헤더에서 전화 단추를 접는다 (2026-09-11 사장님: "저 전화 아이콘은 빼도 되잖아").
좁은 화면 헤더에 상호 + 플레이어 3개 + 전화 + 메뉴가 서서 난잡했다.
@ -258,7 +258,7 @@
문장 길이가 달라 줄이 오갈 히어로 아래가 튀는 것을 막는다. */
/* 대표 문구 아래에서 도는 (2026-09-11). 대표 문구보다 작고 옅게 둔다
둘이 같은 크기·같은 농도면 어느 쪽이 숙소의 줄인지 읽힌다. */
.w4d-sub { margin-top: 0.5rem; font-size: var(--fs-sm, 0.9375rem); line-height: 1.6; opacity: 0.75; }
.w4d-sub { margin-top: 0.5rem; font-size: var(--fs-sm, 0.9375rem); line-height: 1.6; opacity: 0.88; }
.w4d-line { display: block; }
.w4d-line--out {
opacity: 0;
@ -349,7 +349,7 @@
flex-wrap: nowrap;
justify-content: flex-start;
gap: 14px;
padding: 6px 4px 14px;
padding: 6px 4px 22px;
/* 렌더러가 div 자체에 mt-4 지운다 지금은 div `.slider-viewport` 안에
들어가 있어, 여백이 화살표 () 아니라 스크롤 상자 안쪽 공간이 됐다.
간격은 화살표 줄의 mt-4(inject.js `startSongsRail`) 대신 준다. */
@ -369,6 +369,7 @@
#songs button:has(> .w4-disc-mini) > .w4-disc-mini + span {
margin-top: 5px;
font-size: 14px;
line-height: 1.4;
}
/* 고른 판만 살짝 들어 올린다 "뽑아 든 판" 표시. 선택 자체(테두리) React 인라인
스타일로 이미 그린다(`boxShadow`); 여기선 위에 얹는 동작만 더한다. */
@ -383,6 +384,13 @@
#songs .slider-viewport {
-webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
/* 원판이 위아래로 잘렸다 (2026-09-18 대표: "원판 아직도 잘린다.. overflow hidden풀어").
안쪽 :has() div padding 늘려도 소용없었다 실제로 자르는 경계는 뷰포트다.
`.slider-viewport` overflow-x hidden/auto 정하는데, 브라우저는 축이라도
visible 아니면 나머지 축도 auto 계산한다(CSS Overflow 스펙) 세로도 상자의
padding-box 에서 잘린다. 판이 hover·선택으로 최대 6px 떠오르고 그림자가 위로
번지므로, 뷰포트 자체에 여유를 준다. */
padding-block: 14px 4px;
}
/*
@ -449,7 +457,7 @@
border: 1px solid var(--tpl-border, #bcb49e);
resize: none; /* 2026-09-14 대표: "textarea 크기 못바꾸게" — 늘리면 옆 캔버스와 높이가 어긋난다 */
}
.w4d-pm-count { margin: 4px 0 14px; font-size: 14px; text-align: right; opacity: 0.85; }
.w4d-pm-count { margin: 4px 0 14px; font-size: 14px; text-align: right; opacity: 0.92; }
.w4d-pm-actions { display: flex; flex-wrap: wrap; gap: 10px; }
.w4d-pm-actions button {
padding: 9px 16px;
@ -460,7 +468,7 @@
}
.w4d-pm-share { background: var(--tpl-primary, #1b1a15); color: var(--tpl-bg, #fff); }
.w4d-pm-save { background: transparent; color: var(--tpl-text, #1b1a15); }
.w4d-pm-hint { margin: 10px 0 0; font-size: 14px; opacity: 0.6; }
.w4d-pm-hint { margin: 10px 0 0; font-size: 14px; opacity: 0.8; }
@media (max-width: 480px) {
.w4d-pm-canvas { width: 100%; }
}
@ -508,7 +516,7 @@
letter-spacing: 0.18em;
color: color-mix(in oklab, var(--tpl-accent, #bf2f1b) 62%, var(--tpl-text, #1b1a15));
}
.w4d-read-year { font-weight: 400; letter-spacing: 0.06em; opacity: 0.7; }
.w4d-read-year { font-weight: 400; letter-spacing: 0.06em; opacity: 0.85; }
.w4d-read-card h3 {
margin: 0 0 10px;
padding-bottom: 10px;
@ -524,7 +532,7 @@
line-height: 1.8;
text-align: justify;
word-break: keep-all;
opacity: 0.88;
opacity: 0.94;
}
.w4d-read-src {
margin-top: 14px;
@ -532,7 +540,7 @@
border-top: 1px dashed var(--tpl-border, #bcb49e);
font-size: 14px;
font-style: italic;
opacity: 0.6;
opacity: 0.8;
}
.w4d-read-src a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
/* .w4d-read-flag(확인필요 배지) 삭제 2026-09-14 대표: "확인필요 없애고 링크는
@ -588,7 +596,7 @@
color: var(--tpl-bg, #fff);
font-weight: 700;
}
.w4d-gal-total { margin-left: 8px; font-size: 14px; opacity: 0.85; }
.w4d-gal-total { margin-left: 8px; font-size: 14px; opacity: 0.92; }
}
/* 768~1023px 에서 하단 탭바가 푸터 마지막 줄을 덮는 막기

View File

@ -767,7 +767,7 @@
});
html += '</ul>';
}
html += '<span class="w4d-read-counter text-current/55 text-[length:var(--fs-xs)] tabular-nums">' +
html += '<span class="w4d-read-counter text-current/90 text-[length:var(--fs-xs)] tabular-nums">' +
'<span data-cur>1</span> / ' + picked.length + '</span></div>';
var panel = document.createElement('div');
@ -1009,7 +1009,7 @@
// 카운터 — Carousel.tsx 도 점(dots)이 12개를 넘으면 점은 숨기고 이 숫자 줄만 남긴다.
// 여기는 25~50곡이라 항상 그 경우다.
var counter = document.createElement('p');
counter.className = 'mt-2 text-center text-current/55 text-[length:var(--fs-xs)] tabular-nums';
counter.className = 'mt-2 text-center text-current/90 text-[length:var(--fs-xs)] tabular-nums';
counter.innerHTML = '<span data-cur>1</span> / ' + track.children.length;
viewport.parentNode.insertBefore(counter, viewport.nextSibling);
@ -1876,7 +1876,7 @@
if (footer.querySelector('a[href="' + HREF + '"]')) return true; // 번들이 이미 그렸다
var box = footer.querySelector('.shell') || footer.firstElementChild || footer;
var p = document.createElement('p');
p.className = 'pt-3 text-[length:var(--fs-xs)] opacity-55';
p.className = 'pt-3 text-[length:var(--fs-xs)] opacity-85';
p.innerHTML = '<a href="' + HREF + '" target="_blank" rel="noopener noreferrer" ' +
'class="underline-offset-2 hover:underline">AI O2O</a>의 Web4Ai로 만든 사이트입니다.';
box.appendChild(p);

View File

@ -453,9 +453,9 @@ for _section in payload["theme"]["sections"]:
# **있는데 꺼진** 상태라 `hasSection()` 이 참이고, 그 분기는 "사장님이 끈 것" 으로 존중한다.
# ★ 지어낸 값이 없다. 요금·인원·취소 규정은 payload 의 확인된 fact 뿐이고
# (`derive.ts stayBookingView`), 근거가 하나도 없으면 섹션째 안 그린다.
for _section in payload["theme"]["sections"]:
if _section["id"] == "booking":
_section["enabled"] = True
# for _section in payload["theme"]["sections"]:
# if _section["id"] == "booking":
# _section["enabled"] = True
# ── 네이버 플레이스 → 네이버 예약 ────────────────────────────────────────────
for _link in payload["links"]:
@ -466,6 +466,12 @@ for _link in payload["links"]:
# ── index.html 조립 ──────────────────────────────────────────────────────────
html = (SP / "orig" / "index.html").read_text(encoding="utf-8")
_FADED = {"50": "85", "55": "85", "60": "85", "65": "90", "70": "90", "75": "90",
"80": "95", "85": "95"}
html = re.sub(r"(?<![-\w])opacity-(\d+)(?![-\w])",
lambda m: "opacity-" + _FADED.get(m.group(1), m.group(1)), html)
html = html.replace("text-current/55", "text-current/90").replace("text-current/80", "text-current/90")
# :root 의 --tpl-* 색상은 payload 의 window.__SITE_PAYLOAD__ 와 별도로 굳어 있던 값이다
# (원본 프리렌더 시점 그대로) — theme.colors 를 바꿔도 이 블록은 그대로라 배경색 변경이
# 절반만 반영됐다(소개 섹션은 --tpl-surface, 이용안내는 --tpl-surface-alt 를 쓴다).
@ -477,7 +483,11 @@ _colors = payload["theme"]["colors"]
_tpl_vars = {
"--tpl-primary": _colors["primary"], "--tpl-secondary": _colors["secondary"],
"--tpl-bg": _colors["bg"], "--tpl-card": _colors["card"], "--tpl-text": _colors["text"],
"--tpl-accent": _colors["accent"], "--tpl-surface": _colors["surface"],
"--tpl-accent": _colors["accent"],
# ★ 원본 payload(stay-payload.json)에는 surface 키가 **없다** — 프리렌더가 :root 에 박아
# 둔 고정값이 유일한 출처다(실측 #e5dcc6). 대괄호로 읽으면 KeyError 로 굽기가 통째로
# 멈춘다(2026-09-18 실측: 이 줄에서 죽어 /s/stay 를 다시 구울 수 없었다).
"--tpl-surface": _colors.get("surface", "#e5dcc6"),
"--tpl-surface-alt": _colors["card"],
}
for _name, _value in _tpl_vars.items():
@ -509,7 +519,7 @@ assert OLD_UPDATED_AT not in html and OLD_UPDATED_AT[:10] not in html, "옛 날
# ★ 화면이 실제로 따라오는 것은 `vendor/` 번들을 다시 복사한 다음이다(아래 ★ 갱신 절차) —
# 지금 넣는 것은 크롤러와 첫 화면이 보는 값이고, 날짜를 payload 밖에서도 고치는 것과
# 같은 이유다.
MADE_BY = ('<p class="pt-3 text-[length:var(--fs-xs)] opacity-55">'
MADE_BY = ('<p class="pt-3 text-[length:var(--fs-xs)] opacity-85">'
'<a href="https://www.o2osolution.ai/" target="_blank" rel="noopener noreferrer" '
'class="underline-offset-2 hover:underline">AI O2O</a>'
'의 Web4Ai로 만든 사이트입니다.</p>')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long