diff --git a/app/main.py b/app/main.py index ccf5c02..8c0470f 100644 --- a/app/main.py +++ b/app/main.py @@ -42,6 +42,27 @@ async def redirect_http_to_https(request: Request, call_next): return await call_next(request) +@app.middleware("http") +async def support_head_requests(request: Request, call_next): + # 이 Starlette 버전은 GET 라우트에 HEAD를 자동으로 열어주지 않아 크롤러의 HEAD 요청이 + # 전부 405로 막힌다(구글 OAuth 브랜딩 인증 크롤러가 홈페이지를 HEAD로 먼저 확인하면서 + # "콘텐츠 없음"으로 오판하는 원인이 됐음). GET과 동일하게 처리한 뒤 본문만 비워서 응답한다. + if request.method != "HEAD": + return await call_next(request) + + request.scope["method"] = "GET" + response = await call_next(request) + + async def _empty_body(): + return + yield b"" # pragma: no cover - 제너레이터로 만들기 위한 도달 불가 코드 + + response.body_iterator = _empty_body() + # uvicorn은 실제 전송 바이트와 Content-Length가 다르면 예외를 던지므로 0으로 맞춘다. + response.headers["content-length"] = "0" + return response + + app.mount("/static", StaticFiles(directory="app/static"), name="static") app.include_router(auth.router) diff --git a/app/routers/pages.py b/app/routers/pages.py index 4d53f29..6c58c77 100644 --- a/app/routers/pages.py +++ b/app/routers/pages.py @@ -68,6 +68,14 @@ def privacy_page(request: Request, db: Session = Depends(get_db)): ) +@router.get("/terms") +def terms_page(request: Request, db: Session = Depends(get_db)): + user = get_current_user_optional(request, db) + return templates.TemplateResponse( + request, "terms.html", {"logged_in": user is not None, "current_user": user} + ) + + @router.get("/account") def account_page(request: Request, db: Session = Depends(get_db)): current = _current_user_or_redirect(request, db) diff --git a/app/static/css/style.css b/app/static/css/style.css index d98d601..956f65b 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -121,6 +121,11 @@ p { color: var(--color-text-muted); } +.app-footer a + a::before { + content: "·"; + margin: 0 6px; +} + /* iOS 홈 화면 추가 안내 배너 */ .ios-install-banner { display: none; @@ -1131,10 +1136,12 @@ label { text-align: center; } +/* 앱 이름을 담은 h1 — 시각적으로는 작은 브랜드 라벨이지만 문서 구조상 최상위 제목이다. */ .landing-brand { display: flex; align-items: center; gap: 8px; + margin: 0; font-size: 15px; font-weight: 700; color: var(--color-accent); @@ -1147,7 +1154,7 @@ label { border-radius: 6px; } -.landing-hero h1 { +.landing-hero-title { font-size: 32px; font-weight: 700; letter-spacing: -0.02em; @@ -1263,3 +1270,40 @@ label { font-size: 14.5px; margin-bottom: 2px; } + +/* 기능 설명 / 계정 안내 — 구글 브랜딩 심사가 홈페이지에서 확인하는 "앱의 목적" 설명 영역. */ +.landing-section h2 { + font-size: 17px; + font-weight: 700; + margin: 0 0 var(--space-2); +} + +.landing-list { + margin: 0; + padding-left: 1.1em; + display: flex; + flex-direction: column; + gap: 10px; + font-size: 14.5px; + line-height: 1.65; + color: var(--color-text); +} + +.landing-list strong { + color: var(--color-accent); +} + +.landing-note { + margin: 0 0 var(--space-2); + font-size: 13.5px; + line-height: 1.7; + color: var(--color-text-muted); +} + +.landing-note:last-child { + margin-bottom: 0; +} + +.landing-note strong { + color: var(--color-text); +} diff --git a/app/templates/base.html b/app/templates/base.html index 1dddf70..2ac37db 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,6 +4,8 @@