diff --git a/CHANGELOG.md b/CHANGELOG.md
index 951711f..759302b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -53,6 +53,7 @@
### Changed
+- 데스크톱/웹 스크린샷 캡처를 앱 창·앱 셸 기준으로 정리하고, README·SHOWCASE 이미지를 실제 종횡비에 맞춘 `width/height` 기준으로 재배치
- 공개 원격 배포 정책을 `public/* 브랜치 + 버전 태그 + 릴리즈 페이지 + 자산` 기준으로 고정
- Gitea/GitHub 릴리즈 게시 스크립트가 지정 원격 기준으로 동작하고 최신 스크린샷 자산도 함께 첨부하도록 확장
- 비-`origin` 원격에 대한 로컬 pre-push 가드가 `public/*` 브랜치와 `refs/tags/*`를 함께 허용하도록 조정
diff --git a/README.md b/README.md
index 78cad12..9fc1eb8 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,9 @@
# KoTalk
+
플랫한 화이트 톤과 컴팩트한 창 밀도를 기준으로 정리한 현재 데스크톱 셸
@@ -88,16 +93,53 @@ KoTalk는 이 배경을 리스크 문구로 숨기지 않고, 왜 이 프로젝
현재 저장소에서 바로 볼 수 있는 화면과 산출물은 아래와 같습니다.
-| Surface | What to look at | Visual |
-|---|---|---|
-| Desktop shell | 레일 + 목록 + 대화 중심의 3단 구조, 플랫한 보더, 멀티 윈도우 전제 | [hero-shell.png](docs/assets/latest/hero-shell.png) |
-| Desktop onboarding | 첫 실행 시 서버 주소보다 사용자 흐름을 먼저 보여주는 가벼운 진입 | [onboarding.png](docs/assets/latest/onboarding.png) |
-| Desktop conversation | 메시지 흐름, 읽기 상태, 입력 패널의 조밀한 배치 | [conversation.png](docs/assets/latest/conversation.png) |
-| Mobile web onboarding | 빠른 진입과 한국어 중심의 간결한 가입 흐름 | [vstalk-web-onboarding.png](docs/assets/latest/vstalk-web-onboarding.png) |
-| Mobile web inbox | 최근 대화, 필터, 검색 진입의 기본 구조 | [vstalk-web-list.png](docs/assets/latest/vstalk-web-list.png) |
-| Mobile web search | 대화 재발견과 보관 흐름의 1차 구현 | [vstalk-web-search.png](docs/assets/latest/vstalk-web-search.png) |
-| Mobile web saved | 나중에 답장, 중요 대화, 다시 열기 허브 | [vstalk-web-saved.png](docs/assets/latest/vstalk-web-saved.png) |
-| Mobile web chat | 모바일 입력창, 상단 정보 밀도, 복귀 동선 | [vstalk-web-chat.png](docs/assets/latest/vstalk-web-chat.png) |
+
+
+
+
+ Desktop shell
+ 레일 + 목록 + 대화 중심의 3단 구조
+
+
+
+ Desktop onboarding
+ 첫 진입을 짧게 정리한 온보딩
+
+
+
+
+
+ Desktop conversation
+ 대화 흐름과 입력 패널 밀도
+
+
+
+ Mobile web inbox
+ 최근 대화와 필터 구조
+
+
+
+
+
+
+
+
+ Mobile web onboarding
+
+
+
+ Mobile web search
+
+
+
+ Mobile web saved
+
+
+
+ Mobile web chat
+
+
+
전체 화면 묶음은 [SHOWCASE.md](SHOWCASE.md)에서 더 자세히 볼 수 있습니다.
diff --git a/SHOWCASE.md b/SHOWCASE.md
index 12bbf64..d808c1d 100644
--- a/SHOWCASE.md
+++ b/SHOWCASE.md
@@ -25,11 +25,26 @@ KoTalk의 현재 공개 표면을 짧게 훑어보는 문서가 아니라, 지
### Desktop Screens
-| Screen | Why it matters |
-|---|---|
-| [hero-shell.png](docs/assets/latest/hero-shell.png) | 현재 데스크톱 전체 셸의 구조와 밀도 |
-| [onboarding.png](docs/assets/latest/onboarding.png) | 첫 진입에서 어떤 정보를 먼저 보여주는지 |
-| [conversation.png](docs/assets/latest/conversation.png) | 실제 대화 화면의 읽기 흐름과 입력 밀도 |
+
+
+
+
+ Desktop shell
+ 현재 데스크톱 전체 셸의 구조와 밀도
+
+
+
+ Desktop onboarding
+ 첫 진입에서 먼저 보이는 정보
+
+
+
+
+
+
+ Desktop conversation
+ 실제 대화 화면의 읽기 흐름과 입력 밀도
+
## Mobile Web Walkthrough
@@ -44,13 +59,38 @@ KoTalk의 현재 공개 표면을 짧게 훑어보는 문서가 아니라, 지
### Mobile Web Screens
-| Screen | Why it matters |
-|---|---|
-| [vstalk-web-onboarding.png](docs/assets/latest/vstalk-web-onboarding.png) | 초기 진입과 가입 흐름 |
-| [vstalk-web-list.png](docs/assets/latest/vstalk-web-list.png) | 현재 받은함 구조 |
-| [vstalk-web-search.png](docs/assets/latest/vstalk-web-search.png) | 검색과 재발견 흐름 |
-| [vstalk-web-saved.png](docs/assets/latest/vstalk-web-saved.png) | 보관과 후속조치 허브 |
-| [vstalk-web-chat.png](docs/assets/latest/vstalk-web-chat.png) | 모바일 대화 화면의 현재 밀도 |
+
+
+
+
+ Onboarding
+ 초기 진입과 가입 흐름
+
+
+
+ Inbox
+ 현재 받은함 구조
+
+
+
+ Search
+ 검색과 재발견 흐름
+
+
+
+
+
+ Saved
+ 보관과 후속조치 허브
+
+
+
+ Chat
+ 모바일 대화 화면의 현재 밀도
+
+
+
+
## Build And Artifact Shelf
diff --git a/branding/BRAND_GUIDE.md b/branding/BRAND_GUIDE.md
new file mode 100644
index 0000000..7af1109
--- /dev/null
+++ b/branding/BRAND_GUIDE.md
@@ -0,0 +1,91 @@
+# KoTalk Brand Guide
+
+## Overview
+
+This package turns the approved raster logo concept into a reusable production asset set for KoTalk.
+
+- Reference intent: warm, calm, modern messenger mark
+- Primary use: app icon, favicon, PWA icon, Windows icon, docs, release pages
+- Master source: `branding/kotalk-logo-master.svg`
+
+## File Inventory
+
+### Master vectors
+
+- `branding/kotalk-logo-master.svg`
+- `branding/kotalk-logo-master.pdf`
+- `branding/kotalk-logo-master.eps`
+
+### PNG exports
+
+- Transparent: `branding/png/kotalk-transparent-{1024|512|256|192|180|128|64|32|16}.png`
+- White background: `branding/png/kotalk-white-1024.png`
+- Mono black: `branding/png/kotalk-mono-black-1024.png`
+- Mono white: `branding/png/kotalk-mono-white-1024.png`
+- Inverse for dark surfaces: `branding/png/kotalk-inverse-1024.png`
+
+### ICO exports
+
+- Windows app icon: `branding/ico/kotalk.ico`
+- Favicon bundle: `branding/ico/favicon.ico`
+
+### Applied runtime assets
+
+- Web app icons: `src/PhysOn.Web/public/`
+- Desktop app icons: `src/PhysOn.Desktop/Assets/`
+
+## Color System
+
+### Core colors
+
+- Ink: `#394350`
+- Warm accent: `#F05B2B`
+- Separator white: `#FFFFFF`
+
+### Supporting backgrounds
+
+- Paper: `#F7F3EE`
+- Night: `#141922`
+
+### Recommended backgrounds
+
+- White: `#FFFFFF`
+- Warm paper: `#F7F3EE`
+- Dark surface: `#141922`
+
+## Safe Area
+
+- Artboard: `1024 x 1024`
+- Recommended clear area from the artboard edge: `128px`
+- Do not scale the visible mark so large that any speech bubble or the center chevron approaches the outer 12% of the square
+
+## Minimum Use Size
+
+- Preferred minimum digital size: `24px`
+- Favicon minimum: `16px`
+- When rendering below `24px`, use the packaged ICO or PNG exports instead of re-rasterizing from screenshots
+
+## Usage Rules
+
+- Keep the mark square, centered, and unrotated
+- Use the transparent master for docs and UI surfaces when the background is already controlled
+- Use the white background exports for launcher and touch icons
+- Use the mono variants only where a single-color system is required
+
+## Do Not
+
+- Stretch or rotate the mark
+- Add shadows, glow, gradients, glass, or texture
+- Change the orange or dark brand colors arbitrarily
+- Add pattern backgrounds behind the mark
+- Recreate the logo from screenshots when the packaged vectors are available
+
+## Regeneration
+
+Run:
+
+```bash
+python scripts/branding/generate_kotalk_brand_assets.py
+```
+
+This regenerates the committed vector, PNG, ICO, and app-facing icon assets from the scripted master geometry.
diff --git a/branding/ico/favicon.ico b/branding/ico/favicon.ico
new file mode 100644
index 0000000..186d2e6
Binary files /dev/null and b/branding/ico/favicon.ico differ
diff --git a/branding/ico/kotalk.ico b/branding/ico/kotalk.ico
new file mode 100644
index 0000000..5938741
Binary files /dev/null and b/branding/ico/kotalk.ico differ
diff --git a/branding/kotalk-logo-master.eps b/branding/kotalk-logo-master.eps
new file mode 100644
index 0000000..c1c35ee
--- /dev/null
+++ b/branding/kotalk-logo-master.eps
@@ -0,0 +1,54 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%BoundingBox: 0 0 1024 1024
+%%HiResBoundingBox: 0 0 1024 1024
+%%LanguageLevel: 2
+%%Pages: 1
+%%EndComments
+/roundrect {
+ /r exch def
+ /y2 exch def
+ /x2 exch def
+ /y exch def
+ /x exch def
+ newpath
+ x r add y moveto
+ x2 r sub y lineto
+ x2 r sub y r add r 270 360 arc
+ x2 y2 r sub lineto
+ x2 r sub y2 r sub r 0 90 arc
+ x r add y2 lineto
+ x r add y2 r sub r 90 180 arc
+ x y r add lineto
+ x r add y r add r 180 270 arc
+ closepath
+} def
+gsave
+0 1024 translate
+1 -1 scale
+0.223529 0.262745 0.313725 setrgbcolor
+218.00 312.00 584.00 602.00 22.00 roundrect fill
+newpath
+304.00 602.00 moveto
+304.00 736.00 lineto
+438.00 602.00 lineto
+closepath fill
+0.941176 0.356863 0.168627 setrgbcolor
+446.00 312.00 812.00 602.00 22.00 roundrect fill
+newpath
+668.00 602.00 moveto
+742.00 602.00 lineto
+742.00 694.00 lineto
+694.00 650.00 lineto
+closepath fill
+1.000000 1.000000 1.000000 setrgbcolor
+newpath
+490.00 328.00 moveto
+582.00 328.00 lineto
+446.00 457.00 lineto
+582.00 586.00 lineto
+490.00 586.00 lineto
+338.00 457.00 lineto
+closepath fill
+grestore
+showpage
+%%EOF
diff --git a/branding/kotalk-logo-master.pdf b/branding/kotalk-logo-master.pdf
new file mode 100644
index 0000000..c067e85
--- /dev/null
+++ b/branding/kotalk-logo-master.pdf
@@ -0,0 +1,73 @@
+%PDF-1.7
+%쏢
+%%Invocation: gs -dBATCH -dNOPAUSE -dSAFER -sDEVICE=pdfwrite -sOutputFile=? ?
+5 0 obj
+<>
+stream
+xeK DDc1g$/(B
+dW%~3l%+Xy<ڄZjǒ^ ;w*[Szac
+ɘֻ;cO=Y:Ϊ?{:;To3h7UY.B\QIe` JXgq#v3H8R{O:#A~oJ'h=8|Ϋ Kc.lI08dw6GlnQsƈmendstream
+endobj
+6 0 obj
+260
+endobj
+4 0 obj
+<>
+/Contents 5 0 R
+>>
+endobj
+3 0 obj
+<< /Type /Pages /Kids [
+4 0 R
+] /Count 1
+>>
+endobj
+1 0 obj
+<>
+endobj
+7 0 obj
+<>stream
+
+
+
+
+
+2026-04-16T11:47:43+09:00
+2026-04-16T11:47:43+09:00
+UnknownApplication
+
+Untitled
+
+
+
+
+
+endstream
+endobj
+2 0 obj
+<>endobj
+xref
+0 8
+0000000000 65535 f
+0000000615 00000 n
+0000001938 00000 n
+0000000556 00000 n
+0000000442 00000 n
+0000000093 00000 n
+0000000423 00000 n
+0000000679 00000 n
+trailer
+<< /Size 8 /Root 1 0 R /Info 2 0 R
+/ID []
+>>
+startxref
+2064
+%%EOF
diff --git a/branding/kotalk-logo-master.svg b/branding/kotalk-logo-master.svg
new file mode 100644
index 0000000..9896f14
--- /dev/null
+++ b/branding/kotalk-logo-master.svg
@@ -0,0 +1,7 @@
+
diff --git a/branding/png/kotalk-inverse-1024.png b/branding/png/kotalk-inverse-1024.png
new file mode 100644
index 0000000..e17961a
Binary files /dev/null and b/branding/png/kotalk-inverse-1024.png differ
diff --git a/branding/png/kotalk-mono-black-1024.png b/branding/png/kotalk-mono-black-1024.png
new file mode 100644
index 0000000..7c02434
Binary files /dev/null and b/branding/png/kotalk-mono-black-1024.png differ
diff --git a/branding/png/kotalk-mono-white-1024.png b/branding/png/kotalk-mono-white-1024.png
new file mode 100644
index 0000000..98cbd38
Binary files /dev/null and b/branding/png/kotalk-mono-white-1024.png differ
diff --git a/branding/png/kotalk-transparent-1024.png b/branding/png/kotalk-transparent-1024.png
new file mode 100644
index 0000000..f69aab6
Binary files /dev/null and b/branding/png/kotalk-transparent-1024.png differ
diff --git a/branding/png/kotalk-transparent-128.png b/branding/png/kotalk-transparent-128.png
new file mode 100644
index 0000000..3dcc310
Binary files /dev/null and b/branding/png/kotalk-transparent-128.png differ
diff --git a/branding/png/kotalk-transparent-16.png b/branding/png/kotalk-transparent-16.png
new file mode 100644
index 0000000..a1b3415
Binary files /dev/null and b/branding/png/kotalk-transparent-16.png differ
diff --git a/branding/png/kotalk-transparent-180.png b/branding/png/kotalk-transparent-180.png
new file mode 100644
index 0000000..338190f
Binary files /dev/null and b/branding/png/kotalk-transparent-180.png differ
diff --git a/branding/png/kotalk-transparent-192.png b/branding/png/kotalk-transparent-192.png
new file mode 100644
index 0000000..7513fbe
Binary files /dev/null and b/branding/png/kotalk-transparent-192.png differ
diff --git a/branding/png/kotalk-transparent-256.png b/branding/png/kotalk-transparent-256.png
new file mode 100644
index 0000000..ff8e096
Binary files /dev/null and b/branding/png/kotalk-transparent-256.png differ
diff --git a/branding/png/kotalk-transparent-32.png b/branding/png/kotalk-transparent-32.png
new file mode 100644
index 0000000..20f35d5
Binary files /dev/null and b/branding/png/kotalk-transparent-32.png differ
diff --git a/branding/png/kotalk-transparent-512.png b/branding/png/kotalk-transparent-512.png
new file mode 100644
index 0000000..4630d03
Binary files /dev/null and b/branding/png/kotalk-transparent-512.png differ
diff --git a/branding/png/kotalk-transparent-64.png b/branding/png/kotalk-transparent-64.png
new file mode 100644
index 0000000..d3a287b
Binary files /dev/null and b/branding/png/kotalk-transparent-64.png differ
diff --git a/branding/png/kotalk-white-1024.png b/branding/png/kotalk-white-1024.png
new file mode 100644
index 0000000..c874e5f
Binary files /dev/null and b/branding/png/kotalk-white-1024.png differ
diff --git a/branding/reference/kotalk-logo-reference.png b/branding/reference/kotalk-logo-reference.png
new file mode 100644
index 0000000..eb74857
Binary files /dev/null and b/branding/reference/kotalk-logo-reference.png differ
diff --git a/docs/assets/latest/README.md b/docs/assets/latest/README.md
index 4538802..92c83bc 100644
--- a/docs/assets/latest/README.md
+++ b/docs/assets/latest/README.md
@@ -8,6 +8,8 @@
- 새 릴리즈나 큰 UI 변경이 있으면 이 폴더의 스크린샷도 함께 갱신합니다.
- 릴리즈 번들용 스크린샷과 별개로, 저장소 안의 최신 기준 화면을 유지합니다.
- 모바일 웹 스크린샷은 `scripts/ci/capture-vstalk-web-screenshots.cjs`로 다시 생성할 수 있습니다.
+- 캡처는 불필요한 바깥 여백 없이 앱 창 또는 앱 셸 자체만 정확히 담아야 합니다.
+- README, SHOWCASE, 릴리즈 페이지에 게시할 때는 실제 이미지 종횡비를 유지하는 `width/height` 기준으로 사용합니다.
- 현재 포함:
- Windows 데스크톱 셸
- Windows 온보딩/대화 화면
diff --git a/docs/assets/latest/conversation.png b/docs/assets/latest/conversation.png
index 8cf9d3c..070955c 100644
Binary files a/docs/assets/latest/conversation.png and b/docs/assets/latest/conversation.png differ
diff --git a/docs/assets/latest/hero-shell.png b/docs/assets/latest/hero-shell.png
index f0fccd9..8f91153 100644
Binary files a/docs/assets/latest/hero-shell.png and b/docs/assets/latest/hero-shell.png differ
diff --git a/docs/assets/latest/onboarding.png b/docs/assets/latest/onboarding.png
index d033cd1..b229d53 100644
Binary files a/docs/assets/latest/onboarding.png and b/docs/assets/latest/onboarding.png differ
diff --git a/docs/assets/latest/vstalk-web-chat.png b/docs/assets/latest/vstalk-web-chat.png
index eb05981..198da65 100644
Binary files a/docs/assets/latest/vstalk-web-chat.png and b/docs/assets/latest/vstalk-web-chat.png differ
diff --git a/docs/assets/latest/vstalk-web-list.png b/docs/assets/latest/vstalk-web-list.png
index ed91abc..e749ce5 100644
Binary files a/docs/assets/latest/vstalk-web-list.png and b/docs/assets/latest/vstalk-web-list.png differ
diff --git a/docs/assets/latest/vstalk-web-onboarding.png b/docs/assets/latest/vstalk-web-onboarding.png
index a6c59f3..3eab9df 100644
Binary files a/docs/assets/latest/vstalk-web-onboarding.png and b/docs/assets/latest/vstalk-web-onboarding.png differ
diff --git a/docs/assets/latest/vstalk-web-saved.png b/docs/assets/latest/vstalk-web-saved.png
index 2bbb236..b4857dc 100644
Binary files a/docs/assets/latest/vstalk-web-saved.png and b/docs/assets/latest/vstalk-web-saved.png differ
diff --git a/docs/assets/latest/vstalk-web-search.png b/docs/assets/latest/vstalk-web-search.png
index 11fb563..18e4263 100644
Binary files a/docs/assets/latest/vstalk-web-search.png and b/docs/assets/latest/vstalk-web-search.png differ
diff --git a/release-assets/templates/RELEASE_NOTES.ko.md b/release-assets/templates/RELEASE_NOTES.ko.md
index 3cc1c47..77db2c0 100644
--- a/release-assets/templates/RELEASE_NOTES.ko.md
+++ b/release-assets/templates/RELEASE_NOTES.ko.md
@@ -1,4 +1,4 @@
-# vs-messanger {{VERSION}}
+# KoTalk {{VERSION}}
- 채널: `{{CHANNEL}}`
- 게시 시각: `{{PUBLISHED_AT}}`
diff --git a/scripts/branding/generate_kotalk_brand_assets.py b/scripts/branding/generate_kotalk_brand_assets.py
new file mode 100644
index 0000000..dfba2e6
--- /dev/null
+++ b/scripts/branding/generate_kotalk_brand_assets.py
@@ -0,0 +1,353 @@
+#!/usr/bin/env python3
+
+from __future__ import annotations
+
+import math
+import subprocess
+from pathlib import Path
+
+from PIL import Image, ImageDraw
+
+
+ROOT = Path(__file__).resolve().parents[2]
+BRANDING_DIR = ROOT / "branding"
+REFERENCE_DIR = BRANDING_DIR / "reference"
+PNG_DIR = BRANDING_DIR / "png"
+ICO_DIR = BRANDING_DIR / "ico"
+WEB_PUBLIC_DIR = ROOT / "src" / "PhysOn.Web" / "public"
+DESKTOP_ASSETS_DIR = ROOT / "src" / "PhysOn.Desktop" / "Assets"
+
+ARTBOARD = 1024
+DARK = "#394350"
+WARM = "#F05B2B"
+WHITE = "#FFFFFF"
+PAPER = "#F7F3EE"
+NIGHT = "#141922"
+BLACK = "#111111"
+
+LEFT_RECT = (218, 312, 584, 602, 22)
+LEFT_TAIL = [(304, 602), (304, 736), (438, 602)]
+RIGHT_RECT = (446, 312, 812, 602, 22)
+RIGHT_TAIL = [(668, 602), (742, 602), (742, 694), (694, 650)]
+CHEVRON = [(490, 328), (582, 328), (446, 457), (582, 586), (490, 586), (338, 457)]
+
+
+def ensure_dirs() -> None:
+ for path in (BRANDING_DIR, REFERENCE_DIR, PNG_DIR, ICO_DIR, WEB_PUBLIC_DIR, DESKTOP_ASSETS_DIR):
+ path.mkdir(parents=True, exist_ok=True)
+
+
+def hex_to_rgba(hex_value: str, alpha: int = 255) -> tuple[int, int, int, int]:
+ hex_value = hex_value.lstrip("#")
+ return tuple(int(hex_value[index : index + 2], 16) for index in (0, 2, 4)) + (alpha,)
+
+
+def rounded_rect_path(x: float, y: float, width: float, height: float, radius: float) -> str:
+ right = x + width
+ bottom = y + height
+ return (
+ f"M {x + radius:.2f} {y:.2f} "
+ f"H {right - radius:.2f} "
+ f"A {radius:.2f} {radius:.2f} 0 0 1 {right:.2f} {y + radius:.2f} "
+ f"V {bottom - radius:.2f} "
+ f"A {radius:.2f} {radius:.2f} 0 0 1 {right - radius:.2f} {bottom:.2f} "
+ f"H {x + radius:.2f} "
+ f"A {radius:.2f} {radius:.2f} 0 0 1 {x:.2f} {bottom - radius:.2f} "
+ f"V {y + radius:.2f} "
+ f"A {radius:.2f} {radius:.2f} 0 0 1 {x + radius:.2f} {y:.2f} Z"
+ )
+
+
+def polygon_path(points: list[tuple[float, float]]) -> str:
+ start_x, start_y = points[0]
+ segments = [f"M {start_x:.2f} {start_y:.2f}"]
+ for x, y in points[1:]:
+ segments.append(f"L {x:.2f} {y:.2f}")
+ segments.append("Z")
+ return " ".join(segments)
+
+
+def svg_document(
+ *,
+ background: str | None,
+ left_fill: str,
+ right_fill: str,
+ chevron_fill: str,
+) -> str:
+ background_markup = (
+ f'\n '
+ if background
+ else ""
+ )
+ return f"""
+"""
+
+
+def ps_color(hex_value: str) -> str:
+ r, g, b, _ = hex_to_rgba(hex_value)
+ return f"{r / 255:.6f} {g / 255:.6f} {b / 255:.6f} setrgbcolor"
+
+
+def ps_polygon(points: list[tuple[float, float]]) -> str:
+ lines = ["newpath"]
+ start_x, start_y = points[0]
+ lines.append(f"{start_x:.2f} {start_y:.2f} moveto")
+ for x, y in points[1:]:
+ lines.append(f"{x:.2f} {y:.2f} lineto")
+ lines.append("closepath fill")
+ return "\n".join(lines)
+
+
+def eps_document() -> str:
+ x1, y1, x2, y2, r = LEFT_RECT
+ x1b, y1b, x2b, y2b, rb = RIGHT_RECT
+ return f"""%!PS-Adobe-3.0 EPSF-3.0
+%%BoundingBox: 0 0 {ARTBOARD} {ARTBOARD}
+%%HiResBoundingBox: 0 0 {ARTBOARD} {ARTBOARD}
+%%LanguageLevel: 2
+%%Pages: 1
+%%EndComments
+/roundrect {{
+ /r exch def
+ /y2 exch def
+ /x2 exch def
+ /y exch def
+ /x exch def
+ newpath
+ x r add y moveto
+ x2 r sub y lineto
+ x2 r sub y r add r 270 360 arc
+ x2 y2 r sub lineto
+ x2 r sub y2 r sub r 0 90 arc
+ x r add y2 lineto
+ x r add y2 r sub r 90 180 arc
+ x y r add lineto
+ x r add y r add r 180 270 arc
+ closepath
+}} def
+gsave
+0 {ARTBOARD} translate
+1 -1 scale
+{ps_color(DARK)}
+{x1:.2f} {y1:.2f} {x2:.2f} {y2:.2f} {r:.2f} roundrect fill
+{ps_polygon(LEFT_TAIL)}
+{ps_color(WARM)}
+{x1b:.2f} {y1b:.2f} {x2b:.2f} {y2b:.2f} {rb:.2f} roundrect fill
+{ps_polygon(RIGHT_TAIL)}
+{ps_color(WHITE)}
+{ps_polygon(CHEVRON)}
+grestore
+showpage
+%%EOF
+"""
+
+
+def draw_variant(
+ size: int,
+ *,
+ background: str | None,
+ left_fill: str,
+ right_fill: str,
+ chevron_fill: str,
+ supersample: int = 4,
+) -> Image.Image:
+ render_size = size * supersample
+ scale = render_size / ARTBOARD
+ image = Image.new("RGBA", (render_size, render_size), (0, 0, 0, 0))
+ draw = ImageDraw.Draw(image)
+
+ if background:
+ draw.rectangle((0, 0, render_size, render_size), fill=hex_to_rgba(background))
+
+ left = [value * scale for value in LEFT_RECT[:4]]
+ right = [value * scale for value in RIGHT_RECT[:4]]
+ radius = LEFT_RECT[4] * scale
+ right_radius = RIGHT_RECT[4] * scale
+
+ draw.rounded_rectangle(left, radius=radius, fill=hex_to_rgba(left_fill))
+ draw.polygon([(x * scale, y * scale) for x, y in LEFT_TAIL], fill=hex_to_rgba(left_fill))
+
+ draw.rounded_rectangle(right, radius=right_radius, fill=hex_to_rgba(right_fill))
+ draw.polygon([(x * scale, y * scale) for x, y in RIGHT_TAIL], fill=hex_to_rgba(right_fill))
+
+ draw.polygon([(x * scale, y * scale) for x, y in CHEVRON], fill=hex_to_rgba(chevron_fill))
+
+ if supersample == 1:
+ return image
+
+ return image.resize((size, size), Image.Resampling.LANCZOS)
+
+
+def write_text_assets() -> None:
+ (BRANDING_DIR / "kotalk-logo-master.svg").write_text(
+ svg_document(background=None, left_fill=DARK, right_fill=WARM, chevron_fill=WHITE),
+ encoding="utf-8",
+ )
+ (BRANDING_DIR / "kotalk-logo-master.eps").write_text(eps_document(), encoding="utf-8")
+
+ subprocess.run(
+ [
+ "gs",
+ "-dBATCH",
+ "-dNOPAUSE",
+ "-dSAFER",
+ "-sDEVICE=pdfwrite",
+ f"-sOutputFile={BRANDING_DIR / 'kotalk-logo-master.pdf'}",
+ str(BRANDING_DIR / "kotalk-logo-master.eps"),
+ ],
+ check=True,
+ cwd=ROOT,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ )
+
+ transparent_svg = svg_document(background=None, left_fill=DARK, right_fill=WARM, chevron_fill=WHITE)
+ mono_svg = svg_document(background=None, left_fill=BLACK, right_fill=BLACK, chevron_fill=BLACK)
+ inverse_svg = svg_document(background=NIGHT, left_fill=PAPER, right_fill=WARM, chevron_fill=NIGHT)
+
+ (WEB_PUBLIC_DIR / "icon.svg").write_text(transparent_svg, encoding="utf-8")
+ (WEB_PUBLIC_DIR / "vs-mark.svg").write_text(transparent_svg, encoding="utf-8")
+ (WEB_PUBLIC_DIR / "mask-icon.svg").write_text(inverse_svg, encoding="utf-8")
+ (WEB_PUBLIC_DIR / "apple-touch-icon.svg").write_text(transparent_svg, encoding="utf-8")
+
+
+def write_png_assets() -> None:
+ transparent_sizes = [1024, 512, 256, 192, 180, 128, 64, 32, 16]
+ for size in transparent_sizes:
+ transparent = draw_variant(
+ size,
+ background=None,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ )
+ transparent.save(PNG_DIR / f"kotalk-transparent-{size}.png")
+
+ draw_variant(
+ 1024,
+ background=WHITE,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ ).save(PNG_DIR / "kotalk-white-1024.png")
+ draw_variant(
+ 1024,
+ background=None,
+ left_fill=BLACK,
+ right_fill=BLACK,
+ chevron_fill=BLACK,
+ ).save(PNG_DIR / "kotalk-mono-black-1024.png")
+ draw_variant(
+ 1024,
+ background=None,
+ left_fill=WHITE,
+ right_fill=WHITE,
+ chevron_fill=WHITE,
+ ).save(PNG_DIR / "kotalk-mono-white-1024.png")
+ draw_variant(
+ 1024,
+ background=NIGHT,
+ left_fill=PAPER,
+ right_fill=WARM,
+ chevron_fill=NIGHT,
+ ).save(PNG_DIR / "kotalk-inverse-1024.png")
+
+ draw_variant(
+ 512,
+ background=WHITE,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ ).save(WEB_PUBLIC_DIR / "icon-512.png")
+ draw_variant(
+ 192,
+ background=WHITE,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ ).save(WEB_PUBLIC_DIR / "icon-192.png")
+ draw_variant(
+ 180,
+ background=WHITE,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ ).save(WEB_PUBLIC_DIR / "apple-touch-icon.png")
+ draw_variant(
+ 32,
+ background=None,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ ).save(WEB_PUBLIC_DIR / "favicon-32x32.png")
+ draw_variant(
+ 16,
+ background=None,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ ).save(WEB_PUBLIC_DIR / "favicon-16x16.png")
+ draw_variant(
+ 128,
+ background=None,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ ).save(DESKTOP_ASSETS_DIR / "kotalk-mark-128.png")
+
+
+def write_ico_assets() -> None:
+ desktop_icon = draw_variant(
+ 256,
+ background=None,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ )
+ desktop_icon.save(
+ ICO_DIR / "kotalk.ico",
+ format="ICO",
+ sizes=[(256, 256), (128, 128), (64, 64), (48, 48), (32, 32), (16, 16)],
+ )
+ desktop_icon.save(
+ DESKTOP_ASSETS_DIR / "kotalk.ico",
+ format="ICO",
+ sizes=[(256, 256), (128, 128), (64, 64), (48, 48), (32, 32), (16, 16)],
+ )
+
+ favicon_icon = draw_variant(
+ 64,
+ background=None,
+ left_fill=DARK,
+ right_fill=WARM,
+ chevron_fill=WHITE,
+ supersample=6,
+ )
+ favicon_icon.save(
+ ICO_DIR / "favicon.ico",
+ format="ICO",
+ sizes=[(64, 64), (32, 32), (16, 16)],
+ )
+ favicon_icon.save(
+ WEB_PUBLIC_DIR / "favicon.ico",
+ format="ICO",
+ sizes=[(64, 64), (32, 32), (16, 16)],
+ )
+
+
+def main() -> None:
+ ensure_dirs()
+ write_text_assets()
+ write_png_assets()
+ write_ico_assets()
+ print("Generated KoTalk brand assets in branding/, src/PhysOn.Web/public/, and src/PhysOn.Desktop/Assets/.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/ci/capture-kotalk-desktop-screenshots.sh b/scripts/ci/capture-kotalk-desktop-screenshots.sh
index 6adb920..4c846e8 100755
--- a/scripts/ci/capture-kotalk-desktop-screenshots.sh
+++ b/scripts/ci/capture-kotalk-desktop-screenshots.sh
@@ -149,12 +149,8 @@ PY
capture_window() {
local window_id="$1"
local target_path="$2"
- local root_capture="${target_path%.*}-root.${target_path##*.}"
- local geometry
- read -r crop_x crop_y crop_w crop_h < <(wait_for_geometry "$window_id")
- import -window root "$root_capture"
- convert "$root_capture" -crop "${crop_w}x${crop_h}+${crop_x}+${crop_y}" +repage "$target_path"
- rm -f "$root_capture"
+ wait_for_geometry "$window_id" >/dev/null
+ import -window "$window_id" "$target_path"
}
create_conversation_fallback() {
diff --git a/scripts/ci/capture-vstalk-web-screenshots.cjs b/scripts/ci/capture-vstalk-web-screenshots.cjs
index 0c27692..576ae29 100644
--- a/scripts/ci/capture-vstalk-web-screenshots.cjs
+++ b/scripts/ci/capture-vstalk-web-screenshots.cjs
@@ -315,9 +315,9 @@ async function captureOnboarding(browser) {
window.localStorage.clear()
})
await page.reload({ waitUntil: 'networkidle2' })
- await page.screenshot({
+ const app = await page.waitForSelector('.onboarding')
+ await app.screenshot({
path: path.join(outputDir, 'vstalk-web-onboarding.png'),
- fullPage: false,
})
await page.close()
}
@@ -327,9 +327,9 @@ async function captureConversationList(browser) {
await installSessionMocks(page)
await page.goto(baseUrl, { waitUntil: 'networkidle2' })
await page.waitForSelector('.conversation-row')
- await page.screenshot({
+ const app = await page.waitForSelector('.shell')
+ await app.screenshot({
path: path.join(outputDir, 'vstalk-web-list.png'),
- fullPage: false,
})
await page.close()
}
@@ -341,9 +341,9 @@ async function captureConversation(browser) {
await page.waitForSelector('.conversation-row')
await page.click('.conversation-row')
await page.waitForSelector('.message-bubble')
- await page.screenshot({
+ const app = await page.waitForSelector('.shell')
+ await app.screenshot({
path: path.join(outputDir, 'vstalk-web-chat.png'),
- fullPage: false,
})
await page.close()
}
@@ -355,9 +355,9 @@ async function captureSearch(browser) {
await page.waitForSelector('.bottom-bar')
await page.click('.bottom-bar .nav-button:nth-child(2)')
await page.waitForSelector('.search-field')
- await page.screenshot({
+ const app = await page.waitForSelector('.shell')
+ await app.screenshot({
path: path.join(outputDir, 'vstalk-web-search.png'),
- fullPage: false,
})
await page.close()
}
@@ -369,9 +369,9 @@ async function captureSaved(browser) {
await page.waitForSelector('.bottom-bar')
await page.click('.bottom-bar .nav-button:nth-child(3)')
await page.waitForSelector('.saved-section')
- await page.screenshot({
+ const app = await page.waitForSelector('.shell')
+ await app.screenshot({
path: path.join(outputDir, 'vstalk-web-saved.png'),
- fullPage: false,
})
await page.close()
}
diff --git a/scripts/release/release-prepare-assets.sh b/scripts/release/release-prepare-assets.sh
index 90efd56..2e413ee 100755
--- a/scripts/release/release-prepare-assets.sh
+++ b/scripts/release/release-prepare-assets.sh
@@ -166,6 +166,8 @@ write_platform_version_json() {
local body="$2"
cat > "$path" < "$release_root/version.json" <net8.0
enableenable
+ KoTalkapp.manifest
+ Assets\kotalk.ico
+ PHYSIA
+ PHYSIA
+ KoTalk
+ 한국어 중심의 차분한 메시징 경험을 다시 설계하는 Windows-first 메신저
+ KoTalk
+ 0.1.0.5
+ 0.1.0.5
+ 0.1.0-alpha.5
+ 0.1.0-alpha.5true
diff --git a/src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs b/src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs
index 7b90428..e9ce4c6 100644
--- a/src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs
+++ b/src/PhysOn.Desktop/ViewModels/MainWindowViewModel.cs
@@ -329,7 +329,7 @@ public partial class MainWindowViewModel : ViewModelBase, IAsyncDisposable
$"desktop-{Environment.MachineName.ToLowerInvariant()}",
"windows",
Environment.MachineName,
- "0.1.0-alpha.4"));
+ "0.1.0-alpha.5"));
var response = await _apiClient.RegisterAlphaQuickAsync(apiBaseUrl, request, CancellationToken.None);
ApiBaseUrl = apiBaseUrl;
diff --git a/src/PhysOn.Desktop/Views/ConversationWindow.axaml b/src/PhysOn.Desktop/Views/ConversationWindow.axaml
index fb6bbba..1cb4b89 100644
--- a/src/PhysOn.Desktop/Views/ConversationWindow.axaml
+++ b/src/PhysOn.Desktop/Views/ConversationWindow.axaml
@@ -7,55 +7,55 @@
Height="748"
MinWidth="340"
MinHeight="520"
- Background="#F3F4F6"
+ Background="#F7F3EE"
Title="{Binding ConversationTitle}">
@@ -67,14 +67,14 @@
VerticalAlignment="Center"
Text="{Binding ConversationGlyph}"
FontWeight="SemiBold"
- Foreground="#111418" />
+ Foreground="#20242B" />
diff --git a/src/PhysOn.Desktop/Views/MainWindow.axaml b/src/PhysOn.Desktop/Views/MainWindow.axaml
index 7a798ce..fb42249 100644
--- a/src/PhysOn.Desktop/Views/MainWindow.axaml
+++ b/src/PhysOn.Desktop/Views/MainWindow.axaml
@@ -14,7 +14,7 @@
Height="900"
MinWidth="980"
MinHeight="640"
- Background="#F3F4F6">
+ Background="#F7F3EE">
@@ -24,112 +24,112 @@
@@ -137,8 +137,8 @@
-
-
+
+
@@ -162,16 +162,16 @@
@@ -215,13 +215,9 @@
-
-
+
+
@@ -250,7 +246,7 @@
@@ -273,12 +269,9 @@
-
-
+
+
+ Foreground="#20242B" />
@@ -456,7 +449,7 @@
VerticalAlignment="Center"
Text="{Binding SelectedConversationGlyph}"
FontWeight="SemiBold"
- Foreground="#111418" />
+ Foreground="#20242B" />
@@ -503,7 +496,7 @@
IsVisible="{Binding HasErrorText}">
@@ -526,7 +519,7 @@
IsVisible="{Binding ShowSenderName}" />
diff --git a/src/PhysOn.Desktop/app.manifest b/src/PhysOn.Desktop/app.manifest
index 3f899bb..bf9bf9f 100644
--- a/src/PhysOn.Desktop/app.manifest
+++ b/src/PhysOn.Desktop/app.manifest
@@ -3,7 +3,7 @@
-
+
diff --git a/src/PhysOn.Web/index.html b/src/PhysOn.Web/index.html
index 0bb9404..827db92 100644
--- a/src/PhysOn.Web/index.html
+++ b/src/PhysOn.Web/index.html
@@ -3,7 +3,7 @@
-
+
@@ -11,8 +11,11 @@
name="description"
content="업무 대화와 일상 대화를 같은 흐름 안에서 가볍게 이어 주는 KoTalk 웹앱입니다."
/>
+
-
+
+
+
KoTalk
diff --git a/src/PhysOn.Web/package-lock.json b/src/PhysOn.Web/package-lock.json
index ca9700d..524a1e5 100644
--- a/src/PhysOn.Web/package-lock.json
+++ b/src/PhysOn.Web/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "physon-web",
- "version": "0.1.0-alpha.4",
+ "version": "0.1.0-alpha.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "physon-web",
- "version": "0.1.0-alpha.4",
+ "version": "0.1.0-alpha.5",
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
diff --git a/src/PhysOn.Web/package.json b/src/PhysOn.Web/package.json
index 0bebf94..b66ad08 100644
--- a/src/PhysOn.Web/package.json
+++ b/src/PhysOn.Web/package.json
@@ -1,7 +1,7 @@
{
"name": "physon-web",
"private": true,
- "version": "0.1.0-alpha.4",
+ "version": "0.1.0-alpha.5",
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0",
diff --git a/src/PhysOn.Web/public/apple-touch-icon.png b/src/PhysOn.Web/public/apple-touch-icon.png
new file mode 100644
index 0000000..984add4
Binary files /dev/null and b/src/PhysOn.Web/public/apple-touch-icon.png differ
diff --git a/src/PhysOn.Web/public/apple-touch-icon.svg b/src/PhysOn.Web/public/apple-touch-icon.svg
index 68b3e31..9896f14 100644
--- a/src/PhysOn.Web/public/apple-touch-icon.svg
+++ b/src/PhysOn.Web/public/apple-touch-icon.svg
@@ -1,5 +1,7 @@
-