o2o-site-AEO/solution/site/scripts/mockup/inject.css
Mina Choi 43ba33dd65 [feat] site/scripts: /s/stay 시연본 조립 도구 일습 — 일정 생성기·주입분·검증
이 목업은 payload 가 없어 프리렌더 재굽기 대상이 아니다 — `index.html` 한 장이 유일본이고
자산이 지워지면 사람이 되돌려 넣어야 한다(CLAUDE.md 의 ★★ 함정). 지금까지 그 한 장을
이 맥에서만 만들고 있었다. 다른 사람이 이어받을 수 있도록 재료를 전부 올린다.
`build/index.html` 은 생성물이라 이그노어 그대로다 — `patch_stay.py` 로 다시 나온다.

- build_itinerary.py: 테마 21개 일정 생성. 좌표에서 이동시간을 계산하고(도보 4km/h,
  1.5km 초과는 차 25km/h + 주차 5분) 입·퇴실·끼니 창을 맞춘다. 규칙 14종 감사가
  **빌드 안**에 있어 하나라도 어기면 payload 를 쓰지 않고 멈춘다 — 검사가 빌드 밖에
  있던 동안 뼈대를 고칠 때마다 안 보는 규칙이 생겼다(2026-09-11 REVIEW)
- build_story.py: 노래 25곡·인물 57명. 사진은 위키백과 문서 pageimages 만 믿는다
  (이름 검색은 동명이인을 끌고 온다 — 이수현→걸그룹, 박성현→골퍼)
- patch_stay.py: 캐치프레이즈 100개·자작곡 5곡·객실 사진(A동 12/B동 10)을 넣고
  payload 를 갈아 끼운 뒤 inject.css/js 를 `</body>` 앞에 주입해 index.html 을 짠다
- inject.js/css: React 가 다시 그려도 살아남아야 하는 다섯 가지(캐치프레이즈 순환·
  헤더 미니 플레이어·지도 링크·사진 저작자 표시·카로셀 제어). `#root` 밖에 둔다
- audit-all.mjs / rails-test.mjs: 실제 브라우저로 34종 검사, 레일 13개 자동 넘김 전수
- README.md: 이어받는 사람이 먼저 읽는 문서. 배포·함정·§7 "내가 틀렸던 6가지"
- PROMPTS.md / TEXT.md / REVIEW-2026-09-11.md: 문구 생성 프롬프트 · 전체 텍스트 · 검수 보고

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 10:28:52 +09:00

330 lines
12 KiB
CSS

/* /s/stay 목업 주입분 — 헤더 미니 플레이어 · 캐치프레이즈 전환.
★ 색·테두리·서체를 전부 템플릿 변수(--tpl-*)에서 가져온다. 값을 박으면 템플릿을 바꿀 때
플레이어만 옛 색으로 남는다.
★ 플레이어는 헤더 **안**이다. 하단 바는 모바일 탭바(fixed bottom:0 · 61px)를 덮었고,
상단 바는 헤더와 두 줄이 됐다. 헤더 안이면 화면에서 차지하는 줄이 늘지 않는다. */
/* ── 헤더에 꽂는 아이콘 세 개 ─────────────────────────────────────── */
#w4d-mini {
display: inline-flex;
align-items: center;
gap: 2px;
margin-right: 6px;
padding-right: 8px;
border-right: 1px solid var(--tpl-border, #bcb49e);
}
#w4d-mini button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
color: var(--tpl-text, #1b1a15);
background: transparent;
border: 0;
border-radius: var(--tpl-radius, 0);
cursor: pointer;
opacity: 0.75;
}
#w4d-mini button:hover { opacity: 1; background: rgb(27 26 21 / 0.06); }
#w4d-mini svg { width: 16px; height: 16px; }
/* 지금 나오는 곡명. 좁은 화면에서는 헤더에 자리가 없어 접는다 —
실측(390px): 상호 115px + 오른쪽 묶음 94px 이라 남는 폭이 55px 뿐이다.
그 화면에서는 목록을 열면 재생 중인 줄에 곡명과 시간이 있다. */
#w4d-now {
max-width: 150px;
overflow: hidden;
font-size: 11.5px;
font-weight: 700;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--tpl-accent, #bf2f1b);
padding-right: 2px;
}
#w4d-mini[data-playing='0'] #w4d-now { opacity: 0.55; font-weight: 400; }
@media (max-width: 860px) { #w4d-now { display: none; } }
/* 카세트 — 단추가 아니라 표지다. 이 자리가 무엇인지 한눈에 말해 준다. */
#w4d-tape {
display: inline-flex;
align-items: center;
padding: 0 4px 0 2px;
color: var(--tpl-text, #1b1a15);
opacity: 0.8;
}
#w4d-tape svg { width: 20px; height: 20px; }
#w4d-mini[data-playing='1'] #w4d-tape { color: var(--tpl-accent, #bf2f1b); opacity: 1; }
/* 재생 중에는 릴이 돈다 — 소리가 나고 있다는 것을 글자 없이 말한다. */
#w4d-mini[data-playing='1'] .w4d-reel {
transform-box: fill-box;
transform-origin: center;
animation: w4d-reel 2.6s linear infinite;
}
@keyframes w4d-reel { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .w4d-reel { animation: none !important; } }
/* 재생 중일 때만 강조색 — 헤더에 곡명을 안 두므로 색이 유일한 '지금 나온다' 표시다. */
#w4d-mini button[data-playing='1'] { color: var(--tpl-accent, #bf2f1b); opacity: 1; }
@media (max-width: 400px) {
#w4d-mini { margin-right: 2px; padding-right: 4px; }
#w4d-mini button { width: 27px; height: 27px; }
}
/* ── 목록 패널 (헤더 아래에 떠 있다) ──────────────────────────────
★ 좁은 화면에서는 좌우 8px 만 남기고 펼친다. 320px 고정이었을 때 390px 화면에서
제목이 두 줄로 접히고 시간이 잘렸다(2026-09-11 사장님: "모바일 대응이 안 되어 있음"). */
#w4d-panel {
position: fixed;
z-index: 45; /* 사이트 헤더가 z-40 이라 그 위 */
width: 344px;
max-width: calc(100vw - 16px);
max-height: min(64vh, 520px);
display: flex;
flex-direction: column;
overflow: hidden;
font-family: var(--tpl-font-body, serif);
color: var(--tpl-text, #1b1a15);
background: var(--tpl-card, #efe7d3);
border: var(--tpl-border-width, 2px) solid var(--tpl-primary, #1b1a15);
box-shadow: var(--tpl-shadow, 4px 4px 0 rgb(27 26 21 / 0.16));
}
#w4d-panel[hidden] { display: none; }
@media (max-width: 640px) { #w4d-panel { width: auto; } }
#w4d-panel .w4d-head {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
padding: 11px 8px 11px 14px;
border-bottom: var(--tpl-border-width, 2px) solid var(--tpl-primary, #1b1a15);
}
.w4d-head-t { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; }
.w4d-head-n { flex: 1 1 auto; font-size: 11px; opacity: 0.5; }
#w4d-panel .w4d-head button {
display: inline-flex;
width: 28px;
height: 28px;
flex: 0 0 auto;
align-items: center;
justify-content: center;
color: inherit;
background: transparent;
border: 0;
cursor: pointer;
opacity: 0.6;
}
#w4d-panel .w4d-head button:hover { opacity: 1; }
#w4d-panel .w4d-head svg { width: 14px; height: 14px; }
#w4d-panel ol {
flex: 1 1 auto;
margin: 0;
padding: 4px 0;
overflow-y: auto;
list-style: none;
-webkit-overflow-scrolling: touch;
}
/* 한 줄 = 판 + 제목/가수 + 시간. 48px 아래로 내리지 않는다 — 손가락으로 누르는 자리다. */
.w4d-item {
display: flex;
width: 100%;
min-height: 52px;
align-items: center;
gap: 11px;
padding: 8px 14px;
color: inherit;
background: transparent;
border: 0;
font: inherit;
text-align: left;
cursor: pointer;
}
.w4d-item + .w4d-item { border-top: 1px dashed var(--tpl-border, #bcb49e); }
.w4d-item:hover { background: rgb(27 26 21 / 0.05); }
.w4d-item[aria-current='true'] { background: rgb(27 26 21 / 0.06); }
.w4d-item[aria-current='true'] .w4d-t { color: var(--tpl-accent, #bf2f1b); }
/* 판 자리. 재생 중인 줄에서는 판 대신 막대가 뛴다. */
.w4d-mark { position: relative; flex: 0 0 26px; height: 26px; }
.w4d-disc-sm {
position: absolute;
inset: 0;
border-radius: 50%;
background:
radial-gradient(circle at 50% 50%, var(--w4d-lbl, #bf2f1b) 0 34%, transparent 34%),
repeating-radial-gradient(circle at 50% 50%, rgb(255 255 255 / 0.12) 0 1px, transparent 1px 3px),
#14110f;
}
.w4d-disc-sm::after {
content: '';
position: absolute;
inset: 45%;
border-radius: 50%;
background: var(--tpl-card, #efe7d3);
}
.w4d-eq {
position: absolute;
inset: 0;
display: none;
align-items: flex-end;
justify-content: center;
gap: 3px;
padding-bottom: 4px;
}
.w4d-eq i {
width: 3px;
height: 6px;
background: var(--tpl-accent, #bf2f1b);
animation: w4d-eq 0.9s ease-in-out infinite;
}
.w4d-eq i:nth-child(2) { animation-delay: 0.18s; }
.w4d-eq i:nth-child(3) { animation-delay: 0.36s; }
@keyframes w4d-eq { 0%, 100% { height: 5px; } 50% { height: 17px; } }
.w4d-item[data-playing='1'] .w4d-disc-sm { display: none; }
.w4d-item[data-playing='1'] .w4d-eq { display: flex; }
@media (prefers-reduced-motion: reduce) { .w4d-eq i { animation: none; height: 11px; } }
.w4d-info { min-width: 0; flex: 1 1 auto; }
.w4d-t {
display: block;
overflow: hidden;
font-size: 13.5px;
font-weight: 700;
white-space: nowrap;
text-overflow: ellipsis;
}
.w4d-sub {
display: block;
margin-top: 2px;
overflow: hidden;
font-size: 11px;
white-space: nowrap;
text-overflow: ellipsis;
opacity: 0.55;
}
.w4d-time {
flex: 0 0 auto;
font-size: 11px;
opacity: 0.6;
font-variant-numeric: tabular-nums;
}
/* 가사 — 우리 곡만. 종이 위 인쇄물처럼 둔다. */
#w4d-lyrics-box {
padding: 10px 12px 14px;
border-top: 1px dashed var(--tpl-border, #bcb49e);
white-space: pre-line;
font-size: 12px;
line-height: 1.9;
}
#w4d-lyrics-box[hidden] { display: none; }
/* ★ 이용 정보 맨 아래 가로선을 지운다 (2026-09-10 사장님 지시).
<hr> 이 아니라 바로 뒤 '예약은 아래로 받습니다' 블록의 border-t 다 — 마지막 항목 밑에 선이
한 줄 더 그어져 빈 칸이 하나 더 있는 것처럼 보인다. 항목 자체는 그대로 둔다.
마크업을 고치면 하이드레이션이 되돌리므로 CSS 로 끈다. */
#root div.border-line.flex.flex-col.gap-3.border-t.pt-6 { border-top-width: 0; }
/* 사진 저작자 표시 — 출처 줄과 같은 크기, 한 단 더 옅게. */
.w4d-credit { margin-top: 2px; font-size: 10px; opacity: 0.55; }
/* ★ 모바일 헤더에서 전화 단추를 접는다 (2026-09-11 사장님: "저 전화 아이콘은 빼도 되잖아").
좁은 화면 헤더에 상호 + 플레이어 3개 + 전화 + 메뉴가 다 서서 난잡했다.
★ 전화가 사라지는 게 아니다 — 이 화면 폭에서는 **하단 탭바에 전화가 이미 있다**
(실측: NAV fixed bottom:0 · 위치 · 전화 · 네이버 예약). 같은 것이 두 번 서 있던 것이다.
★ 넓은 화면에서는 탭바가 없으므로 그대로 둔다. */
@media (max-width: 860px) {
#root header .shell > div:last-child > a[href^="tel:"] { display: none; }
}
/* 덧붙인 링크 — 본문 글자보다 눈에 띄되 강조색을 남발하지 않는다. */
.w4d-link {
color: var(--tpl-accent, #bf2f1b);
text-decoration: underline;
text-underline-offset: 3px;
white-space: nowrap;
}
/* ── 캐치프레이즈 등장 ────────────────────────────────────────────
★ 줄 전체를 페이드하지 않고 **단어가 마스크 안에서 올라온다.**
한 줄짜리 문장은 통째로 흐려지면 '바뀌었다'만 보이고 문장이 안 읽힌다 —
왼쪽부터 차례로 서면 눈이 첫 단어에서 읽기를 시작한다.
★ 나갈 때는 반대로 위로 빠지며 흐려진다. 들어오는 방향과 나가는 방향이 같으면
두 문장이 같은 자리에서 겹쳐 보인다.
★ 자리 흔들림: 처음 구워진 줄 높이를 min-height 로 박아 둔다(스크립트가 잰다).
문장 길이가 달라 한 줄↔두 줄이 오갈 때 히어로 아래가 튀는 것을 막는다. */
.w4d-line { display: block; }
.w4d-line--out {
opacity: 0;
transform: translateY(-5px);
filter: blur(3px);
transition: opacity 0.3s ease, transform 0.3s ease, filter 0.3s ease;
}
.w4d-line--in { opacity: 1; transform: none; filter: none; transition: opacity 0.3s ease, transform 0.3s ease, filter 0.3s ease; }
/* 단어 한 칸 = 마스크. 안쪽 글자가 아래에서 올라온다. */
.w4d-w { display: inline-block; overflow: hidden; vertical-align: bottom; }
.w4d-w > span {
display: inline-block;
transform: translateY(112%);
opacity: 0;
filter: blur(4px);
}
.w4d-line--in .w4d-w > span {
animation: w4d-rise 0.62s cubic-bezier(0.16, 0.84, 0.28, 1) forwards;
animation-delay: var(--w4d-d, 0ms);
}
@keyframes w4d-rise {
from { transform: translateY(112%); opacity: 0; filter: blur(4px); }
60% { filter: blur(0); }
to { transform: translateY(0); opacity: 1; filter: blur(0); }
}
@media (prefers-reduced-motion: reduce) {
.w4d-line--out, .w4d-line--in { transition: none; opacity: 1; transform: none; filter: none; }
.w4d-w > span { transform: none; opacity: 1; filter: none; animation: none; }
}
/* 가사 — 우리 곡만. 종이 위 인쇄물처럼 둔다. */
#w4d-lyrics-box {
max-width: 1120px;
margin: 0 auto;
padding: 12px 16px 16px;
border-top: 1px dashed var(--tpl-border, #bcb49e);
white-space: pre-line;
font-size: 13px;
line-height: 1.9;
max-height: 40vh;
overflow-y: auto;
}
#w4d-lyrics-box[hidden] { display: none; }
/*
* 일정 정거장 번호 — 제목 글자와 세로 중심을 맞춘다.
*
* 번호 동그라미는 inline-grid 라 **안에 든 숫자(10px)의 베이스라인**이 제목(14px)의
* 베이스라인에 붙는다. 숫자가 17px 상자 한가운데 있으니 동그라미 중심은 그만큼 아래로
* 내려가고, 렌더러가 얹은 translate-y-px 가 1px 더 내린다.
* 실측(1280·390 공통): 동그라미 중심이 제목 줄 중심보다 3.14px 아래.
* 2px 올리면 0.14px — 반올림해서 눈에 안 걸리는 자리다.
*/
#itinerary ol li span.inline-grid.rounded-full {
translate: 0 -2px;
transform: none;
}
/*
* 날씨 그림 — 움직임만 끈다 (2026-09-11, 사장님: "날씨 애니메이션 지워줘")
* 그림 자체는 남는다. `prefers-reduced-motion` 에서 보이는 것과 같은 상태다
* (weather.css 가 "멈춘 상태도 그림으로 성립해야 한다"는 전제로 그려져 있다).
* 그림까지 없애려면 아래 두 줄의 주석을 푼다.
*/
#weather .w4-sky::before,
#weather .w4-sky::after {
animation: none !important;
}
/* #weather .w4-sky { display: none; } */