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 +

+ KoTalk mark +

+

한국어 중심의 차분한 메시징 경험을 다시 설계하는 오픈소스 프로젝트.

@@ -26,6 +30,7 @@ Project Status · Showcase · Background · + Brand Guide · FAQ · Releases · Trust Center · @@ -44,7 +49,7 @@

- KoTalk desktop shell + KoTalk desktop shell

플랫한 화이트 톤과 컴팩트한 창 밀도를 기준으로 정리한 현재 데스크톱 셸 @@ -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
+ Desktop shell
+ 레일 + 목록 + 대화 중심의 3단 구조 +
+ Desktop onboarding
+ Desktop onboarding
+ 첫 진입을 짧게 정리한 온보딩 +
+ Desktop conversation
+ Desktop conversation
+ 대화 흐름과 입력 패널 밀도 +
+ Mobile web inbox
+ Mobile web inbox
+ 최근 대화와 필터 구조 +
+ + + + + + + + +
+ Mobile web onboarding
+ Mobile web onboarding +
+ Mobile web search
+ Mobile web search +
+ Mobile web saved
+ Mobile web saved +
+ Mobile web chat
+ 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 shell
+ 현재 데스크톱 전체 셸의 구조와 밀도 +
+ Desktop onboarding
+ Desktop onboarding
+ 첫 진입에서 먼저 보이는 정보 +
+ +

+ Desktop conversation
+ 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) | 모바일 대화 화면의 현재 밀도 | + + + + + + + + + + + +
+ Mobile onboarding
+ Onboarding
+ 초기 진입과 가입 흐름 +
+ Mobile inbox
+ Inbox
+ 현재 받은함 구조 +
+ Mobile search
+ Search
+ 검색과 재발견 흐름 +
+ Mobile saved
+ Saved
+ 보관과 후속조치 허브 +
+ Mobile chat
+ 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.lI08dw 6GlnQsƈ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""" + {background_markup} + + + + + +""" + + +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 enable enable + KoTalk app.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.5 true 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 @@ - - + +