o2o-site-AEO/solution/frontend/src/index.css
Mina Choi 64ce467f21 [refactor] postgres-init,solution: DB 구조 재편 — 스키마 해체 · 공용 콘텐츠 한 벌 · 마이그레이션 체계
도메인별 스키마(company·place·fact·local·site·job)를 걷어내고 public 한 벌로 폈다.
스키마 한정자가 붙은 순간부터 ORM·raw SQL·테스트 픽스처가 각자 그 이름을 들고 다녀야 했다.

- 공용 콘텐츠를 한 테이블로 되돌린다. spots·region_stories 를 따로 파 놓고 보니
  같은 성격이 세 곳으로 갈라져 있었다 — `area_contents` 가 처음부터 content_type 으로
  종류를 가르는 설계였고 그걸 쓰면 됐다. 관계(거리·숨김)만 `place_area_refs` 로 남긴다.
- migrations/ + scripts/migrate.py: `init.sql` 은 **DB 를 처음 만들 때만** 돈다. 파일에
  컬럼을 더해도 이미 데이터가 든 DB 에는 반영되지 않는다 — 실제로 TourAPI 가 주변 정보를
  받아 와도 저장할 곳이 없어 축제·맛집이 0건이었고, 화면에는 "그냥 안 나오는 것" 으로만 보였다.
  DECISIONS.md 가 예고한 그대로다("운영 DB 가 생기는 순간 다시 필요해진다").
  Alembic 을 쓰지 않는 이유는 스키마 정의가 이미 두 곳(ORM·init.sql)이라 세 번째를
  더하면 어긋날 자리가 하나 더 생기기 때문이다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:08:02 +09:00

148 lines
6.3 KiB
CSS

@import "tailwindcss";
@import "tw-animate-css";
/* 디자인 토큰(색상·폰트·radius)은 shared 단일 소스에서 관리. site 와 공유 가능. */
@import "@o2o/shared/styles/tokens.css";
@import "@o2o/shared/styles/base.css";
/* 캔버스가 그리는 것은 발행본과 같은 사이트다 — 시안 토큰·유틸을 같은 파일에서 읽는다.
규칙은 전부 `.site-canvas` 안으로 스코프돼 있어 관리자 크롬에는 닿지 않는다. */
@import "@o2o/shared/styles/site.css";
body {
background-color: var(--background);
color: var(--foreground);
font-family: var(--font-sans);
transition: background-color 0.2s ease, color 0.2s ease;
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
}
}
/* 캔버스(발행 사이트 미리보기)는 관리자 토큰이 아니라 템플릿 색을 쓴다.
.site-canvas 안쪽은 --tpl-* 변수로만 색을 잡는다 — 관리자 크롬 색이 새어 들어오지 않게. */
@layer components {
.site-canvas {
color-scheme: light;
background-color: var(--tpl-bg, #ffffff);
color: var(--tpl-text, #09090b);
/* 본문 서체도 토큰이다. 변수가 없으면(빌더 캔버스 등) 지금까지처럼 관리자 본문 서체를 쓴다. */
font-family: var(--tpl-font-body, var(--font-sans));
}
.site-canvas .serif-title {
font-family: var(--tpl-font-heading, 'Noto Serif KR', 'Batang', 'Times New Roman', serif);
}
/* 캔버스 안의 '면'(카드·패널)과 테두리도 토큰을 따른다.
★ bg-white / bg-stone-* 이 캔버스 안에만 82곳 흩어져 있어 파일마다 고치는 대신 여기서 덮는다.
폴백이 원래 Tailwind 값이라, --tpl-* 이 없는 곳에서는 지금까지와 완전히 동일하다.
★ 팔레트를 바꿔도 화면이 안 변하는 원인의 절반이 이 면들이었다(나머지 절반은 섹션 바탕). */
.site-canvas .bg-white {
background-color: var(--tpl-card, #ffffff);
}
.site-canvas .bg-stone-50 {
background-color: var(--tpl-card, var(--color-stone-50));
}
.site-canvas .bg-stone-100 {
background-color: var(--tpl-surface-alt, var(--color-stone-100));
}
/* 테두리는 팔레트 색을 그대로 쓰면 너무 세다 — 30% 만 섞는다. */
.site-canvas .border-stone-200,
.site-canvas .border-stone-300 {
border-color: color-mix(in oklab, var(--tpl-border, var(--color-stone-200)) 30%, transparent);
}
/* serif-title 을 안 쓰는 제목도 제목 서체를 따르게 한다.
★ 폴백이 inherit 이라 --tpl-font-heading 이 없으면 예전과 똑같이 본문 서체를 그대로 물려받는다. */
.site-canvas h1,
.site-canvas h2,
.site-canvas h3,
.site-canvas h4 {
font-family: var(--tpl-font-heading, inherit);
letter-spacing: var(--tpl-heading-tracking, normal);
}
/* ★ 굵기는 폴백을 inherit 로 두지 않는다. 간판체(Gugi)는 굵기가 한 벌뿐이라 700 을 주면
브라우저가 가짜 볼드를 씌워 획이 뭉갠다 — 템플릿이 400 을 지정할 수 있어야 한다. */
.site-canvas :is(h1, h2, h3, h4).tpl-title {
font-weight: var(--tpl-heading-weight, 700);
}
/* 카드·패널 테두리 두께도 템플릿이 정한다. 레트로는 2px 라야 인쇄물처럼 보인다. */
.site-canvas .border {
border-width: var(--tpl-border-width, 1px);
}
}
/* 히어로 제목의 앞말만 갈아 끼운다 — '홈페이지' 는 고정이고 수식어가 돈다.
★ CSS 만으로 돈다(마퀴와 같은 이유). 문구를 **일곱 줄** 쌓는다 — 마지막 줄이 첫 줄의
복제라 -85.714% 에서 0% 로 되감을 때 글자가 튀지 않는다.
★ 줄 높이를 1.25em 으로 못 박는다. 창이 1줄이고 트랙이 7줄이라 한 칸이 정확히 1/7 이어야 한다.
★ 문구 개수(6)와 이 키프레임은 한 몸이다. 문구를 늘리면 여기 stop 도 같이 고쳐야 한다 —
안 고치면 마지막 몇 개가 영영 안 보이거나 빈 줄이 지나간다(LandingPage HEADLINES). */
@keyframes o2o-rotate-6 {
0%, 14% { transform: translateY(0); }
16.67%, 30.67% { transform: translateY(-14.286%); }
33.33%, 47.33% { transform: translateY(-28.571%); }
50%, 64% { transform: translateY(-42.857%); }
66.67%, 80.67% { transform: translateY(-57.143%); }
83.33%, 97.33% { transform: translateY(-71.429%); }
100% { transform: translateY(-85.714%); }
}
.o2o-rotator {
display: block;
height: 1.25em;
overflow: hidden;
}
.o2o-rotator-track {
/* ★ display:block 이 없으면 아무 일도 안 일어난다. 이 요소는 <span> 이라 기본이 inline 이고,
**인라인 요소에는 transform 이 적용되지 않는다** — 애니메이션은 걸려 있는데 화면은 정지다. */
display: block;
animation: o2o-rotate-6 16s cubic-bezier(0.65, 0, 0.35, 1) infinite;
}
.o2o-rotator-track > span {
display: block;
height: 1.25em;
line-height: 1.25;
}
@media (prefers-reduced-motion: reduce) {
.o2o-rotator-track { animation: none; }
}
/* 랜딩 히어로의 발행 사이트 마퀴. **CSS 만으로 돈다** — 타이머를 쓰면 탭이 백그라운드일 때
프레임이 밀려 끊긴 것처럼 보인다. 트랙에 같은 목록을 두 벌 넣고 -50% 까지 밀면 이음매가 없다. */
@keyframes o2o-marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
.o2o-marquee-track {
animation: o2o-marquee var(--marquee-duration, 48s) linear infinite;
}
.o2o-marquee:hover .o2o-marquee-track {
animation-play-state: paused;
}
@media (prefers-reduced-motion: reduce) {
.o2o-marquee-track { animation: none; }
}
/* 수집 진행바 shimmer — 0%(빈 트랙) 구간에서도 '진행 중'이 보이게 한다. */
@keyframes o2o-shimmer {
from { transform: translateX(-100%); }
to { transform: translateX(500%); }
}
.o2o-progress-shimmer {
animation: o2o-shimmer 1.8s ease-in-out infinite;
}
/* 노치/홈 인디케이터 회피 (viewport-fit=cover 와 짝).
화면 맨 위/맨 아래에 붙는 바에만 붙인다 — 스크롤되는 본문에는 쓰지 않는다. */
@layer components {
.safe-t { padding-top: env(safe-area-inset-top); }
.safe-b { padding-bottom: env(safe-area-inset-bottom); }
}