공개: alpha.5 기준선 갱신

This commit is contained in:
Ian 2026-04-16 12:04:40 +09:00
commit 00da320d96
62 changed files with 883 additions and 175 deletions

View file

@ -53,6 +53,7 @@
### Changed
- 데스크톱/웹 스크린샷 캡처를 앱 창·앱 셸 기준으로 정리하고, README·SHOWCASE 이미지를 실제 종횡비에 맞춘 `width/height` 기준으로 재배치
- 공개 원격 배포 정책을 `public/* 브랜치 + 버전 태그 + 릴리즈 페이지 + 자산` 기준으로 고정
- Gitea/GitHub 릴리즈 게시 스크립트가 지정 원격 기준으로 동작하고 최신 스크린샷 자산도 함께 첨부하도록 확장
- 비-`origin` 원격에 대한 로컬 pre-push 가드가 `public/*` 브랜치와 `refs/tags/*`를 함께 허용하도록 조정

View file

@ -1,5 +1,9 @@
# KoTalk
<p align="center">
<img src="branding/png/kotalk-transparent-256.png" alt="KoTalk mark" width="96" height="96">
</p>
<p align="center">
<strong>한국어 중심의 차분한 메시징 경험을 다시 설계하는 오픈소스 프로젝트.</strong>
</p>
@ -26,6 +30,7 @@
<a href="PROJECT_STATUS.md">Project Status</a> ·
<a href="SHOWCASE.md">Showcase</a> ·
<a href="BACKGROUND.md">Background</a> ·
<a href="branding/BRAND_GUIDE.md">Brand Guide</a> ·
<a href="FAQ.md">FAQ</a> ·
<a href="RELEASING.md">Releases</a> ·
<a href="TRUST_CENTER.md">Trust Center</a> ·
@ -44,7 +49,7 @@
</table>
<p align="center">
<img src="docs/assets/latest/hero-shell.png" alt="KoTalk desktop shell" width="920">
<img src="docs/assets/latest/hero-shell.png" alt="KoTalk desktop shell" width="920" height="652">
</p>
<p align="center">
<em>플랫한 화이트 톤과 컴팩트한 창 밀도를 기준으로 정리한 현재 데스크톱 셸</em>
@ -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) |
<table>
<tr>
<td align="center">
<img src="docs/assets/latest/hero-shell.png" alt="Desktop shell" width="420" height="298"><br>
<strong>Desktop shell</strong><br>
<sub>레일 + 목록 + 대화 중심의 3단 구조</sub>
</td>
<td align="center">
<img src="docs/assets/latest/onboarding.png" alt="Desktop onboarding" width="420" height="298"><br>
<strong>Desktop onboarding</strong><br>
<sub>첫 진입을 짧게 정리한 온보딩</sub>
</td>
</tr>
<tr>
<td align="center">
<img src="docs/assets/latest/conversation.png" alt="Desktop conversation" width="390" height="400"><br>
<strong>Desktop conversation</strong><br>
<sub>대화 흐름과 입력 패널 밀도</sub>
</td>
<td align="center">
<img src="docs/assets/latest/vstalk-web-list.png" alt="Mobile web inbox" width="220" height="513"><br>
<strong>Mobile web inbox</strong><br>
<sub>최근 대화와 필터 구조</sub>
</td>
</tr>
</table>
<table>
<tr>
<td align="center">
<img src="docs/assets/latest/vstalk-web-onboarding.png" alt="Mobile web onboarding" width="220" height="476"><br>
<strong>Mobile web onboarding</strong>
</td>
<td align="center">
<img src="docs/assets/latest/vstalk-web-search.png" alt="Mobile web search" width="220" height="513"><br>
<strong>Mobile web search</strong>
</td>
<td align="center">
<img src="docs/assets/latest/vstalk-web-saved.png" alt="Mobile web saved" width="220" height="513"><br>
<strong>Mobile web saved</strong>
</td>
<td align="center">
<img src="docs/assets/latest/vstalk-web-chat.png" alt="Mobile web chat" width="220" height="513"><br>
<strong>Mobile web chat</strong>
</td>
</tr>
</table>
전체 화면 묶음은 [SHOWCASE.md](SHOWCASE.md)에서 더 자세히 볼 수 있습니다.

View file

@ -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) | 실제 대화 화면의 읽기 흐름과 입력 밀도 |
<table>
<tr>
<td align="center">
<img src="docs/assets/latest/hero-shell.png" alt="Desktop shell" width="520" height="369"><br>
<strong>Desktop shell</strong><br>
<sub>현재 데스크톱 전체 셸의 구조와 밀도</sub>
</td>
<td align="center">
<img src="docs/assets/latest/onboarding.png" alt="Desktop onboarding" width="520" height="369"><br>
<strong>Desktop onboarding</strong><br>
<sub>첫 진입에서 먼저 보이는 정보</sub>
</td>
</tr>
</table>
<p align="center">
<img src="docs/assets/latest/conversation.png" alt="Desktop conversation" width="430" height="441"><br>
<strong>Desktop conversation</strong><br>
<sub>실제 대화 화면의 읽기 흐름과 입력 밀도</sub>
</p>
## 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) | 모바일 대화 화면의 현재 밀도 |
<table>
<tr>
<td align="center">
<img src="docs/assets/latest/vstalk-web-onboarding.png" alt="Mobile onboarding" width="220" height="476"><br>
<strong>Onboarding</strong><br>
<sub>초기 진입과 가입 흐름</sub>
</td>
<td align="center">
<img src="docs/assets/latest/vstalk-web-list.png" alt="Mobile inbox" width="220" height="513"><br>
<strong>Inbox</strong><br>
<sub>현재 받은함 구조</sub>
</td>
<td align="center">
<img src="docs/assets/latest/vstalk-web-search.png" alt="Mobile search" width="220" height="513"><br>
<strong>Search</strong><br>
<sub>검색과 재발견 흐름</sub>
</td>
</tr>
<tr>
<td align="center">
<img src="docs/assets/latest/vstalk-web-saved.png" alt="Mobile saved" width="220" height="513"><br>
<strong>Saved</strong><br>
<sub>보관과 후속조치 허브</sub>
</td>
<td align="center">
<img src="docs/assets/latest/vstalk-web-chat.png" alt="Mobile chat" width="220" height="513"><br>
<strong>Chat</strong><br>
<sub>모바일 대화 화면의 현재 밀도</sub>
</td>
<td></td>
</tr>
</table>
## Build And Artifact Shelf

91
branding/BRAND_GUIDE.md Normal file
View file

@ -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.

BIN
branding/ico/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
branding/ico/kotalk.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -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

View file

@ -0,0 +1,73 @@
%PDF-1.7
%Çì<C387>¢
%%Invocation: gs -dBATCH -dNOPAUSE -dSAFER -sDEVICE=pdfwrite -sOutputFile=? ?
5 0 obj
<</Length 6 0 R/Filter /FlateDecode>>
stream
xœeKŽÄ D÷œcŽ1gˆ”Þ$‹™û/šž(B
dWÊ%~3l%ƒ®Ø÷+Á†X¹Öy‡Œ<Ú„ZjÇÿ^ ÷<C3B7>¯ÔƒÏ;w*[óSš÷€zaÌc
ìɘˆ¬þÁµˆÖ»¡ê¨¾ÑîŒÏ;c—O=Y:Ϊ?{•ÍÃùäð:æÿ;T<C5BD>âo—3Šh7UY.Ž™à \ãQIãeé` ËJ°X©ýgq#vý„3ãHÐ8R{²O:æÌô#Aó~oÜÚJ<C39A>ˆ—޳'h©=8|†ŽùÿΫ ¬KcÐ.lI08dw 6ŠGlö<6C>«nQs¤ŸôÏÆˆmendstream
endobj
6 0 obj
260
endobj
4 0 obj
<</Type/Page/MediaBox [0 0 595 842]
/Parent 3 0 R
/Resources<</ProcSet[/PDF]
>>
/Contents 5 0 R
>>
endobj
3 0 obj
<< /Type /Pages /Kids [
4 0 R
] /Count 1
>>
endobj
1 0 obj
<</Type /Catalog /Pages 3 0 R
/Metadata 7 0 R
>>
endobj
7 0 obj
<</Type/Metadata
/Subtype/XML/Length 1183>>stream
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
<?adobe-xap-filters esc="CRLF"?>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='XMP toolkit 2.9.1-13, framework 1.6'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:iX='http://ns.adobe.com/iX/1.0/'>
<rdf:Description rdf:about="" xmlns:pdf='http://ns.adobe.com/pdf/1.3/' pdf:Producer='GPL Ghostscript 10.02.1'/>
<rdf:Description rdf:about="" xmlns:xmp='http://ns.adobe.com/xap/1.0/'><xmp:ModifyDate>2026-04-16T11:47:43+09:00</xmp:ModifyDate>
<xmp:CreateDate>2026-04-16T11:47:43+09:00</xmp:CreateDate>
<xmp:CreatorTool>UnknownApplication</xmp:CreatorTool></rdf:Description>
<rdf:Description rdf:about="" xmlns:xapMM='http://ns.adobe.com/xap/1.0/mm/' xapMM:DocumentID='uuid:310abc84-715b-11fc-0000-9524caef6f27'/>
<rdf:Description rdf:about="" xmlns:dc='http://purl.org/dc/elements/1.1/' dc:format='application/pdf'><dc:title><rdf:Alt><rdf:li xml:lang='x-default'>Untitled</rdf:li></rdf:Alt></dc:title></rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end='w'?>
endstream
endobj
2 0 obj
<</Producer(GPL Ghostscript 10.02.1)
/CreationDate(D:20260416114743+09'00')
/ModDate(D:20260416114743+09'00')>>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 [<C1EC8B35441ABCA0F74E37FC93F2BE37><C1EC8B35441ABCA0F74E37FC93F2BE37>]
>>
startxref
2064
%%EOF

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none">
<path d="M 240.00 312.00 H 780.00 A 22.00 22.00 0 0 1 802.00 334.00 V 892.00 A 22.00 22.00 0 0 1 780.00 914.00 H 240.00 A 22.00 22.00 0 0 1 218.00 892.00 V 334.00 A 22.00 22.00 0 0 1 240.00 312.00 Z" fill="#394350" />
<path d="M 304.00 602.00 L 304.00 736.00 L 438.00 602.00 Z" fill="#394350" />
<path d="M 468.00 312.00 H 1236.00 A 22.00 22.00 0 0 1 1258.00 334.00 V 892.00 A 22.00 22.00 0 0 1 1236.00 914.00 H 468.00 A 22.00 22.00 0 0 1 446.00 892.00 V 334.00 A 22.00 22.00 0 0 1 468.00 312.00 Z" fill="#F05B2B" />
<path d="M 668.00 602.00 L 742.00 602.00 L 742.00 694.00 L 694.00 650.00 Z" fill="#F05B2B" />
<path d="M 490.00 328.00 L 582.00 328.00 L 446.00 457.00 L 582.00 586.00 L 490.00 586.00 L 338.00 457.00 Z" fill="#FFFFFF" />
</svg>

After

Width:  |  Height:  |  Size: 831 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

@ -8,6 +8,8 @@
- 새 릴리즈나 큰 UI 변경이 있으면 이 폴더의 스크린샷도 함께 갱신합니다.
- 릴리즈 번들용 스크린샷과 별개로, 저장소 안의 최신 기준 화면을 유지합니다.
- 모바일 웹 스크린샷은 `scripts/ci/capture-vstalk-web-screenshots.cjs`로 다시 생성할 수 있습니다.
- 캡처는 불필요한 바깥 여백 없이 앱 창 또는 앱 셸 자체만 정확히 담아야 합니다.
- README, SHOWCASE, 릴리즈 페이지에 게시할 때는 실제 이미지 종횡비를 유지하는 `width/height` 기준으로 사용합니다.
- 현재 포함:
- Windows 데스크톱 셸
- Windows 온보딩/대화 화면

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Before After
Before After

View file

@ -1,4 +1,4 @@
# vs-messanger {{VERSION}}
# KoTalk {{VERSION}}
- 채널: `{{CHANNEL}}`
- 게시 시각: `{{PUBLISHED_AT}}`

View file

@ -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'<path d="{rounded_rect_path(0, 0, ARTBOARD, ARTBOARD, 0)}" fill="{background}" />\n '
if background
else ""
)
return f"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {ARTBOARD} {ARTBOARD}" fill="none">
{background_markup}<path d="{rounded_rect_path(*LEFT_RECT)}" fill="{left_fill}" />
<path d="{polygon_path(LEFT_TAIL)}" fill="{left_fill}" />
<path d="{rounded_rect_path(*RIGHT_RECT)}" fill="{right_fill}" />
<path d="{polygon_path(RIGHT_TAIL)}" fill="{right_fill}" />
<path d="{polygon_path(CHEVRON)}" fill="{chevron_fill}" />
</svg>
"""
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()

View file

@ -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() {

View file

@ -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()
}

View file

@ -166,6 +166,8 @@ write_platform_version_json() {
local body="$2"
cat > "$path" <<EOF
{
"productName": "KoTalk",
"publisher": "PHYSIA",
"version": "$version",
"channel": "$channel",
"publishedAt": "$published_at",
@ -202,6 +204,7 @@ if [[ -n "$windows_zip" ]]; then
latest_hash_paths+=("windows/$windows_latest_name")
windows_platform_body="$(cat <<EOF
"name": "KoTalk for Windows",
"kind": "desktop",
"arch": "x64",
"latestUrl": "$download_base_url/windows/latest",
@ -252,9 +255,10 @@ if [[ -n "$android_apk" ]]; then
latest_hash_paths+=("android/$android_latest_name")
android_platform_body="$(cat <<EOF
"name": "KoTalk for Android",
"kind": "mobile",
"arch": "universal",
"packageName": "kr.physia.vsmessenger",
"packageName": "kr.physia.kotalk",
"minSdk": 26,
"latestUrl": "$download_base_url/android/latest",
"apkUrl": "$download_base_url/android/latest/$android_latest_name",
@ -308,6 +312,8 @@ fi
cat > "$release_root/version.json" <<EOF
{
"productName": "KoTalk",
"publisher": "PHYSIA",
"version": "$version",
"channel": "$channel",
"publishedAt": "$published_at",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -4,7 +4,18 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>KoTalk</AssemblyName>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Assets\kotalk.ico</ApplicationIcon>
<Company>PHYSIA</Company>
<Authors>PHYSIA</Authors>
<Product>KoTalk</Product>
<Description>한국어 중심의 차분한 메시징 경험을 다시 설계하는 Windows-first 메신저</Description>
<AssemblyTitle>KoTalk</AssemblyTitle>
<AssemblyVersion>0.1.0.5</AssemblyVersion>
<FileVersion>0.1.0.5</FileVersion>
<Version>0.1.0-alpha.5</Version>
<InformationalVersion>0.1.0-alpha.5</InformationalVersion>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
</PropertyGroup>

View file

@ -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;

View file

@ -7,55 +7,55 @@
Height="748"
MinWidth="340"
MinHeight="520"
Background="#F3F4F6"
Background="#F7F3EE"
Title="{Binding ConversationTitle}">
<Window.Styles>
<Style Selector="Border.surface">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Border.muted">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="BorderBrush" Value="#E8EAEE" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="BorderBrush" Value="#ECE1D6" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Border.bubble">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="9,7" />
<Setter Property="Margin" Value="0,0,0,6" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="BorderBrush" Value="#E4E7EB" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="BorderBrush" Value="#E6D8CC" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style Selector="Border.bubble.mine">
<Setter Property="Background" Value="#EEF1F4" />
<Setter Property="Background" Value="#F0E5D8" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style Selector="Button.icon">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="8,6" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="BorderBrush" Value="#D9DDE2" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="BorderBrush" Value="#DCCFC4" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Button.primary">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="11,8" />
<Setter Property="Background" Value="#111418" />
<Setter Property="Background" Value="#394350" />
<Setter Property="Foreground" Value="#FFFFFF" />
</Style>
<Style Selector="TextBlock.caption">
<Setter Property="FontSize" Value="11.5" />
<Setter Property="Foreground" Value="#69727D" />
<Setter Property="Foreground" Value="#82766D" />
</Style>
<Style Selector="TextBlock.body">
<Setter Property="FontSize" Value="13" />
<Setter Property="Foreground" Value="#111418" />
<Setter Property="Foreground" Value="#20242B" />
</Style>
</Window.Styles>
@ -67,14 +67,14 @@
VerticalAlignment="Center"
Text="{Binding ConversationGlyph}"
FontWeight="SemiBold"
Foreground="#111418" />
Foreground="#20242B" />
</Border>
<StackPanel Grid.Column="1" Spacing="2">
<TextBlock Text="{Binding ConversationTitle}"
FontSize="14.5"
FontWeight="SemiBold"
Foreground="#111418"
Foreground="#20242B"
TextTrimming="CharacterEllipsis" />
<TextBlock Text="{Binding ConversationSubtitle}"
Classes="caption"
@ -97,7 +97,7 @@
Padding="9"
IsVisible="{Binding HasErrorText}">
<TextBlock Text="{Binding ErrorText}"
Foreground="#C62828"
Foreground="#C9573C"
Classes="caption"
TextWrapping="Wrap" />
</Border>

View file

@ -14,7 +14,7 @@
Height="900"
MinWidth="980"
MinHeight="640"
Background="#F3F4F6">
Background="#F7F3EE">
<Design.DataContext>
<vm:MainWindowViewModel />
@ -24,112 +24,112 @@
<Style Selector="Border.surface">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Border.surface-muted">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="BorderBrush" Value="#E8EAEE" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="BorderBrush" Value="#ECE1D6" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Border.rail-surface">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Border.row-card">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#ECEFF3" />
<Setter Property="BorderBrush" Value="#ECE2D8" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Border.row-card.active">
<Setter Property="Background" Value="#F3F5F7" />
<Setter Property="BorderBrush" Value="#D7DCE3" />
<Setter Property="Background" Value="#F4ECE3" />
<Setter Property="BorderBrush" Value="#DCCABA" />
</Style>
<Style Selector="Border.inline-alert">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#D14B3F" />
<Setter Property="BorderBrush" Value="#C9573C" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="10,8" />
</Style>
<Style Selector="Border.status-chip">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="BorderBrush" Value="#E4E7EB" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="BorderBrush" Value="#E6D8CC" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="8,3" />
</Style>
<Style Selector="Border.unread-badge">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#111418" />
<Setter Property="Background" Value="#394350" />
<Setter Property="Padding" Value="6,1" />
</Style>
<Style Selector="Border.message-bubble">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="BorderBrush" Value="#E4E7EB" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="BorderBrush" Value="#E6D8CC" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="MaxWidth" Value="560" />
<Setter Property="Margin" Value="0,0,0,6" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style Selector="Border.message-bubble.mine">
<Setter Property="Background" Value="#EEF1F4" />
<Setter Property="Background" Value="#F0E5D8" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style Selector="Border.message-bubble.pending">
<Setter Property="Opacity" Value="0.72" />
</Style>
<Style Selector="Border.message-bubble.failed">
<Setter Property="BorderBrush" Value="#C9392C" />
<Setter Property="BorderBrush" Value="#C9573C" />
</Style>
<Style Selector="TextBlock.display-title">
<Setter Property="FontSize" Value="30" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="#111418" />
<Setter Property="Foreground" Value="#20242B" />
</Style>
<Style Selector="TextBlock.section-title">
<Setter Property="FontSize" Value="15" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="#111418" />
<Setter Property="Foreground" Value="#20242B" />
</Style>
<Style Selector="TextBlock.body">
<Setter Property="FontSize" Value="12.5" />
<Setter Property="Foreground" Value="#1D232B" />
<Setter Property="Foreground" Value="#2E3640" />
</Style>
<Style Selector="TextBlock.caption">
<Setter Property="FontSize" Value="11.5" />
<Setter Property="Foreground" Value="#69727D" />
<Setter Property="Foreground" Value="#82766D" />
</Style>
<Style Selector="TextBlock.eyebrow">
<Setter Property="FontSize" Value="10.5" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="Foreground" Value="#69727D" />
<Setter Property="Foreground" Value="#8B7B6C" />
</Style>
<Style Selector="TextBox.input">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="11,8" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#D8DDE4" />
<Setter Property="BorderBrush" Value="#DCCFC4" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="13" />
</Style>
<Style Selector="TextBox.search-input">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="9,7" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="13" />
</Style>
<Style Selector="Button.primary-button">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="12,9" />
<Setter Property="Background" Value="#111418" />
<Setter Property="Background" Value="#394350" />
<Setter Property="Foreground" Value="#FFFFFF" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
@ -137,8 +137,8 @@
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="10,8" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="Foreground" Value="#1E252C" />
<Setter Property="BorderBrush" Value="#D8DDE4" />
<Setter Property="Foreground" Value="#394350" />
<Setter Property="BorderBrush" Value="#DCCFC4" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Button.icon-button">
@ -146,9 +146,9 @@
<Setter Property="MinWidth" Value="30" />
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="8,0" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="Foreground" Value="#1E252C" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="Foreground" Value="#394350" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="12" />
</Style>
@ -162,16 +162,16 @@
<Style Selector="Button.filter-button">
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="9,5" />
<Setter Property="Background" Value="#F7F8FA" />
<Setter Property="Foreground" Value="#69727D" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="Background" Value="#FBF7F2" />
<Setter Property="Foreground" Value="#7F736A" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector="Button.filter-button.selected">
<Setter Property="Background" Value="#111418" />
<Setter Property="Background" Value="#394350" />
<Setter Property="Foreground" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#111418" />
<Setter Property="BorderBrush" Value="#394350" />
</Style>
<Style Selector="Button.rail-button">
<Setter Property="Width" Value="38" />
@ -179,15 +179,15 @@
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="0" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="Foreground" Value="#1E252C" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="Foreground" Value="#394350" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="13" />
</Style>
<Style Selector="Button.rail-button.active">
<Setter Property="Background" Value="#111418" />
<Setter Property="Background" Value="#394350" />
<Setter Property="Foreground" Value="#FFFFFF" />
<Setter Property="BorderBrush" Value="#111418" />
<Setter Property="BorderBrush" Value="#394350" />
</Style>
<Style Selector="Button.row-button">
<Setter Property="Background" Value="Transparent" />
@ -199,8 +199,8 @@
<Setter Property="CornerRadius" Value="2" />
<Setter Property="Padding" Value="9,5" />
<Setter Property="Background" Value="#FFFFFF" />
<Setter Property="Foreground" Value="#4A5560" />
<Setter Property="BorderBrush" Value="#E5E7EB" />
<Setter Property="Foreground" Value="#7F736A" />
<Setter Property="BorderBrush" Value="#E8DDD2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="FontSize" Value="11.5" />
</Style>
@ -215,13 +215,9 @@
<StackPanel Spacing="14">
<StackPanel Spacing="8">
<TextBlock Text="KO · TALK" Classes="eyebrow" />
<Border Width="50" Height="50" Classes="surface-muted" HorizontalAlignment="Left">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="KO"
FontSize="18"
FontWeight="Bold"
Foreground="#111418" />
<Border Width="50" Height="50" Classes="surface-muted" HorizontalAlignment="Left" Padding="7">
<Image Source="avares://PhysOn.Desktop/Assets/kotalk-mark-128.png"
Stretch="Uniform" />
</Border>
<TextBlock Text="KoTalk" Classes="display-title" />
</StackPanel>
@ -250,7 +246,7 @@
<Border Classes="inline-alert" IsVisible="{Binding HasErrorText}">
<TextBlock Text="{Binding ErrorText}"
Classes="caption"
Foreground="#C9392C"
Foreground="#C9573C"
TextWrapping="Wrap" />
</Border>
@ -273,12 +269,9 @@
<Border Grid.Column="0" Classes="rail-surface" Padding="8">
<Grid RowDefinitions="Auto,*,Auto">
<StackPanel Spacing="8">
<Border Width="38" Height="38" Classes="surface-muted">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="KO"
FontWeight="Bold"
Foreground="#111418" />
<Border Width="38" Height="38" Classes="surface-muted" Padding="6">
<Image Source="avares://PhysOn.Desktop/Assets/kotalk-mark-128.png"
Stretch="Uniform" />
</Border>
<Button Classes="rail-button active" ToolTip.Tip="받은함">
@ -292,7 +285,7 @@
VerticalAlignment="Center"
Text="{Binding CurrentUserMonogram}"
FontWeight="SemiBold"
Foreground="#1E252C" />
Foreground="#394350" />
</Border>
<Button Classes="rail-button"
ToolTip.Tip="로그아웃"
@ -397,7 +390,7 @@
VerticalAlignment="Center"
Text="{Binding AvatarText}"
FontWeight="SemiBold"
Foreground="#111418" />
Foreground="#20242B" />
</Border>
<TextBlock Grid.Column="1"
@ -444,7 +437,7 @@
<GridSplitter Grid.Column="2"
Width="6"
IsVisible="{Binding IsConversationPaneExpanded}"
Background="#E5E7EB"
Background="#E8DDD2"
ResizeDirection="Columns"
ShowsPreview="True" />
@ -456,7 +449,7 @@
VerticalAlignment="Center"
Text="{Binding SelectedConversationGlyph}"
FontWeight="SemiBold"
Foreground="#111418" />
Foreground="#20242B" />
</Border>
<StackPanel Grid.Column="1" Spacing="2">
@ -503,7 +496,7 @@
IsVisible="{Binding HasErrorText}">
<TextBlock Text="{Binding ErrorText}"
Classes="caption"
Foreground="#C9392C"
Foreground="#C9573C"
TextWrapping="Wrap" />
</Border>
@ -526,7 +519,7 @@
IsVisible="{Binding ShowSenderName}" />
<TextBlock Text="{Binding Text}"
FontSize="13"
Foreground="#111418"
Foreground="#20242B"
TextWrapping="Wrap" />
<TextBlock Text="{Binding MetaText}" Classes="caption" />
</StackPanel>

View file

@ -3,7 +3,7 @@
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="PhysOn.Desktop.Desktop"/>
<assemblyIdentity version="1.0.0.0" name="KoTalk.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>

View file

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="theme-color" content="#101826" />
<meta name="theme-color" content="#394350" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="KoTalk" />
@ -11,8 +11,11 @@
name="description"
content="업무 대화와 일상 대화를 같은 흐름 안에서 가볍게 이어 주는 KoTalk 웹앱입니다."
/>
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" href="/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/apple-touch-icon.svg" />
<link rel="icon" href="/favicon-32x32.png" type="image/png" sizes="32x32" />
<link rel="icon" href="/favicon-16x16.png" type="image/png" sizes="16x16" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
<title>KoTalk</title>
</head>

View file

@ -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"

View file

@ -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",

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,5 +1,7 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="180" height="180" rx="50" fill="#101826"/>
<path d="M42 55C42 47.268 48.268 41 56 41H124C131.732 41 138 47.268 138 55V104C138 111.732 131.732 118 124 118H89.285L65.86 136.409C61.252 140.031 54.55 136.752 54.55 130.896V118H56C48.268 118 42 111.732 42 104V55Z" fill="#F6F2EA"/>
<path d="M63 77.5L78.363 100.438L92.545 77.727L107.223 100.438L122.5 77.5" stroke="#101826" stroke-width="8.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none">
<path d="M 240.00 312.00 H 780.00 A 22.00 22.00 0 0 1 802.00 334.00 V 892.00 A 22.00 22.00 0 0 1 780.00 914.00 H 240.00 A 22.00 22.00 0 0 1 218.00 892.00 V 334.00 A 22.00 22.00 0 0 1 240.00 312.00 Z" fill="#394350" />
<path d="M 304.00 602.00 L 304.00 736.00 L 438.00 602.00 Z" fill="#394350" />
<path d="M 468.00 312.00 H 1236.00 A 22.00 22.00 0 0 1 1258.00 334.00 V 892.00 A 22.00 22.00 0 0 1 1236.00 914.00 H 468.00 A 22.00 22.00 0 0 1 446.00 892.00 V 334.00 A 22.00 22.00 0 0 1 468.00 312.00 Z" fill="#F05B2B" />
<path d="M 668.00 602.00 L 742.00 602.00 L 742.00 694.00 L 694.00 650.00 Z" fill="#F05B2B" />
<path d="M 490.00 328.00 L 582.00 328.00 L 446.00 457.00 L 582.00 586.00 L 490.00 586.00 L 338.00 457.00 Z" fill="#FFFFFF" />
</svg>

Before

Width:  |  Height:  |  Size: 562 B

After

Width:  |  Height:  |  Size: 831 B

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,5 +1,7 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" rx="144" fill="#101826"/>
<path d="M120 150C120 127.909 137.909 110 160 110H352C374.091 110 392 127.909 392 150V304C392 326.091 374.091 344 352 344H251.232L184.314 396.674C171.148 407.041 152 397.656 152 380.899V344H160C137.909 344 120 326.091 120 304V150Z" fill="#F6F2EA"/>
<path d="M171 201.5L214.894 267L255.4 202.15L297.35 267L341 201.5" stroke="#101826" stroke-width="24" stroke-linecap="round" stroke-linejoin="round"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none">
<path d="M 240.00 312.00 H 780.00 A 22.00 22.00 0 0 1 802.00 334.00 V 892.00 A 22.00 22.00 0 0 1 780.00 914.00 H 240.00 A 22.00 22.00 0 0 1 218.00 892.00 V 334.00 A 22.00 22.00 0 0 1 240.00 312.00 Z" fill="#394350" />
<path d="M 304.00 602.00 L 304.00 736.00 L 438.00 602.00 Z" fill="#394350" />
<path d="M 468.00 312.00 H 1236.00 A 22.00 22.00 0 0 1 1258.00 334.00 V 892.00 A 22.00 22.00 0 0 1 1236.00 914.00 H 468.00 A 22.00 22.00 0 0 1 446.00 892.00 V 334.00 A 22.00 22.00 0 0 1 468.00 312.00 Z" fill="#F05B2B" />
<path d="M 668.00 602.00 L 742.00 602.00 L 742.00 694.00 L 694.00 650.00 Z" fill="#F05B2B" />
<path d="M 490.00 328.00 L 582.00 328.00 L 446.00 457.00 L 582.00 586.00 L 490.00 586.00 L 338.00 457.00 Z" fill="#FFFFFF" />
</svg>

Before

Width:  |  Height:  |  Size: 570 B

After

Width:  |  Height:  |  Size: 831 B

Before After
Before After

View file

@ -1,24 +1,24 @@
{
"name": "PhysOn",
"short_name": "PhysOn",
"name": "KoTalk",
"short_name": "KoTalk",
"description": "업무 소통과 친근한 대화를 한 손 흐름으로 이어 주는 모바일 중심 메신저 웹앱",
"start_url": "/",
"display": "standalone",
"background_color": "#101826",
"theme_color": "#101826",
"background_color": "#FFFFFF",
"theme_color": "#394350",
"lang": "ko-KR",
"icons": [
{
"src": "/icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/mask-icon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "maskable"
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}

View file

@ -1,4 +1,8 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="512" height="512" rx="144" fill="#101826"/>
<path d="M120 150C120 127.909 137.909 110 160 110H352C374.091 110 392 127.909 392 150V304C392 326.091 374.091 344 352 344H251.232L184.314 396.674C171.148 407.041 152 397.656 152 380.899V344H160C137.909 344 120 326.091 120 304V150Z" fill="#F6F2EA"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none">
<path d="M 0.00 0.00 H 1024.00 A 0.00 0.00 0 0 1 1024.00 0.00 V 1024.00 A 0.00 0.00 0 0 1 1024.00 1024.00 H 0.00 A 0.00 0.00 0 0 1 0.00 1024.00 V 0.00 A 0.00 0.00 0 0 1 0.00 0.00 Z" fill="#141922" />
<path d="M 240.00 312.00 H 780.00 A 22.00 22.00 0 0 1 802.00 334.00 V 892.00 A 22.00 22.00 0 0 1 780.00 914.00 H 240.00 A 22.00 22.00 0 0 1 218.00 892.00 V 334.00 A 22.00 22.00 0 0 1 240.00 312.00 Z" fill="#F7F3EE" />
<path d="M 304.00 602.00 L 304.00 736.00 L 438.00 602.00 Z" fill="#F7F3EE" />
<path d="M 468.00 312.00 H 1236.00 A 22.00 22.00 0 0 1 1258.00 334.00 V 892.00 A 22.00 22.00 0 0 1 1236.00 914.00 H 468.00 A 22.00 22.00 0 0 1 446.00 892.00 V 334.00 A 22.00 22.00 0 0 1 468.00 312.00 Z" fill="#F05B2B" />
<path d="M 668.00 602.00 L 742.00 602.00 L 742.00 694.00 L 694.00 650.00 Z" fill="#F05B2B" />
<path d="M 490.00 328.00 L 582.00 328.00 L 446.00 457.00 L 582.00 586.00 L 490.00 586.00 L 338.00 457.00 Z" fill="#141922" />
</svg>

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

@ -1,5 +1,12 @@
const CACHE_NAME = 'vs-talk-shell-v2';
const SHELL = ['/manifest.webmanifest', '/icon.svg', '/apple-touch-icon.svg', '/mask-icon.svg'];
const CACHE_NAME = 'ko-talk-shell-v3';
const SHELL = [
'/manifest.webmanifest',
'/icon.svg',
'/icon-192.png',
'/icon-512.png',
'/apple-touch-icon.png',
'/favicon.ico',
];
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL)));

View file

@ -1,5 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none">
<rect width="128" height="128" rx="34" fill="#17372F"/>
<path d="M27 33h23l14 25 14-25h23L75 95H53L27 33Z" fill="#F6E8D5"/>
<path d="M60 39h8v50h-8z" fill="#DDB07B"/>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" fill="none">
<path d="M 240.00 312.00 H 780.00 A 22.00 22.00 0 0 1 802.00 334.00 V 892.00 A 22.00 22.00 0 0 1 780.00 914.00 H 240.00 A 22.00 22.00 0 0 1 218.00 892.00 V 334.00 A 22.00 22.00 0 0 1 240.00 312.00 Z" fill="#394350" />
<path d="M 304.00 602.00 L 304.00 736.00 L 438.00 602.00 Z" fill="#394350" />
<path d="M 468.00 312.00 H 1236.00 A 22.00 22.00 0 0 1 1258.00 334.00 V 892.00 A 22.00 22.00 0 0 1 1236.00 914.00 H 468.00 A 22.00 22.00 0 0 1 446.00 892.00 V 334.00 A 22.00 22.00 0 0 1 468.00 312.00 Z" fill="#F05B2B" />
<path d="M 668.00 602.00 L 742.00 602.00 L 742.00 694.00 L 694.00 650.00 Z" fill="#F05B2B" />
<path d="M 490.00 328.00 L 582.00 328.00 L 446.00 457.00 L 582.00 586.00 L 490.00 586.00 L 338.00 457.00 Z" fill="#FFFFFF" />
</svg>

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 831 B

Before After
Before After

View file

@ -106,6 +106,17 @@
fill: currentColor;
}
.brand-mark__image {
display: block;
width: 18px;
height: 18px;
}
.brand-mark--small .brand-mark__image {
width: 16px;
height: 16px;
}
.brand-mark--small {
width: 32px;
height: 32px;
@ -239,7 +250,7 @@
.field input::placeholder,
.search-field input::placeholder,
.composer textarea::placeholder {
color: #a0a7b1;
color: #aaa095;
}
.text-action,
@ -411,20 +422,20 @@
width: 7px;
height: 7px;
border-radius: 2px;
background: #9ca3af;
background: #b6a292;
}
.status-chip--connecting .status-dot,
.status-chip--idle .status-dot {
background: #f59e0b;
background: #d08a44;
}
.status-chip--connected .status-dot {
background: #1f9d55;
background: #698869;
}
.status-chip--fallback .status-dot {
background: #2563eb;
background: #6c84ab;
}
.search-field {

View file

@ -53,7 +53,7 @@ type IconName =
| 'group'
const DEFAULT_API_BASE_URL = import.meta.env.VITE_API_BASE_URL?.trim() ?? ''
const APP_VERSION = 'web-0.1.0-alpha.4'
const APP_VERSION = 'web-0.1.0-alpha.5'
const CONNECTION_LABEL: Record<ConnectionState, string> = {
idle: '준비 중',
@ -220,7 +220,15 @@ function Icon({ name }: { name: IconName }) {
case 'mark':
return (
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M5 6.5a2.5 2.5 0 0 1 2.5-2.5h9A2.5 2.5 0 0 1 19 6.5v6A2.5 2.5 0 0 1 16.5 15H10l-3.5 3v-3H7.5A2.5 2.5 0 0 1 5 12.5z" />
<path
fill="#394350"
d="M4.2 7.1A1.2 1.2 0 0 1 5.4 5.9h8.4A1.2 1.2 0 0 1 15 7.1v5.8a1.2 1.2 0 0 1-1.2 1.2H10l-2.7 2.7v-2.7H5.4a1.2 1.2 0 0 1-1.2-1.2z"
/>
<path
fill="#F05B2B"
d="M9.8 7.1A1.2 1.2 0 0 1 11 5.9h7.6a1.2 1.2 0 0 1 1.2 1.2v5.8a1.2 1.2 0 0 1-1.2 1.2h-1.1v2.2l-1.8-2.2H11a1.2 1.2 0 0 1-1.2-1.2z"
/>
<path fill="#FFFFFF" d="M11.1 6.8h2L9.8 12l3.3 5.2h-2l-4-5.2z" />
</svg>
)
case 'refresh':
@ -1060,7 +1068,7 @@ function App() {
<header className="onboarding__chrome">
<div className="brand-lockup">
<span className="brand-mark" aria-hidden="true">
<Icon name="mark" />
<img className="brand-mark__image" src="/vs-mark.svg" alt="" />
</span>
<div className="brand-lockup__text">
<strong>KoTalk</strong>
@ -1159,7 +1167,7 @@ function App() {
<header className="appbar">
<div className="appbar__leading">
<span className="brand-mark brand-mark--small" aria-hidden="true">
<Icon name="mark" />
<img className="brand-mark__image" src="/vs-mark.svg" alt="" />
</span>
<div className="appbar__title">
<h2>{activeDestinationMeta.title}</h2>

View file

@ -8,25 +8,25 @@
sans-serif;
line-height: 1.5;
font-weight: 500;
color: #111111;
background: #f5f5f6;
color: #20242b;
background: #f7f3ee;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
--surface-page: #f5f5f6;
--surface-page: #f7f3ee;
--surface-base: #ffffff;
--surface-raised: #fcfcfc;
--surface-muted: #f6f6f7;
--surface-selected: #eef2f6;
--surface-chat-mine: #f1f1f2;
--border-subtle: #ececef;
--border-strong: #d8d8dd;
--border-contrast: #18181b;
--text-strong: #141416;
--text-soft: #333338;
--text-muted: #7b7b84;
--focus-ring: #1a73e8;
--surface-raised: #fdfaf6;
--surface-muted: #faf6f1;
--surface-selected: #f3ece4;
--surface-chat-mine: #f0e5d8;
--border-subtle: #e8ddd2;
--border-strong: #dbcdbf;
--border-contrast: #394350;
--text-strong: #20242b;
--text-soft: #3b414b;
--text-muted: #82766d;
--focus-ring: #c07a43;
}
* {