Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 19 additions & 30 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,33 @@
## ๐Ÿ“‹ ์ž‘์—… ๋‚ด์šฉ
- **์บ๋ฆญํ„ฐ ์ €์žฅ ์นด๋“œ ์ด๋ฏธ์ง€ ํ‘œ์‹œ ๊ฐœ์„ **: `object-cover` ๋ฐฉ์‹์„ ํ•ด์ œํ•˜๊ณ  ์›๋ณธ ์ด๋ฏธ์ง€ ๋น„์œจ์— ๋งž์ถฐ ์นด๋“œ์˜ ์„ธ๋กœ ๊ธธ์ด๊ฐ€ ์ž๋™์œผ๋กœ ๋Š˜์–ด๋‚˜ ์˜จ์ „ํ•œ ์ด๋ฏธ์ง€๊ฐ€ ๋ Œ๋”๋ง๋˜๊ฒŒ ๋ณ€๊ฒฝ (ํ•˜๋‹จ๋ถ€ 1/24 ์˜์—ญ ํด๋ฆฌํ•‘)
- **์นด๋“œ ์—ฌ๋ฐฑ ๋ฐ ๊ตฌ๋ถ„์„  ์ •๋ฆฌ**: ์บ๋ฆญํ„ฐ์™€ ์ด๋ฆ„ ์‚ฌ์ด์˜ ๊ฐ€๋กœ ๊ตฌ๋ถ„์„ ๊ณผ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ํ•˜๋‹จ ์–ด๋‘์šด ๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐ•์Šค ์˜ค๋ฒ„๋ ˆ์ด๋ฅผ ์‚ญ์ œํ•ด ๊น”๋”ํ•œ UI ๊ตฌ์„ฑ
- **์นด๋“œ ๋„ˆ๋น„ ๋ฐ ์—ฌ๋ฐฑ ์ตœ์ ํ™”**: ๋ชจ๋ฐ”์ผ ๋ทฐ ๋Œ€๋น„ ์นด๋“œ๊ฐ€ ํ™”๋ฉด์„ ๊ฐ€๋ฆฌ๋Š” ํ˜„์ƒ์„ ๊ณ ์ณ ์นด๋“œ ๋„ˆ๋น„๋ฅผ `max-w-[260px]`๋กœ ์ถ•์†Œํ•˜๊ณ , ์นด๋“œ์™€ ํ•˜๋‹จ ๊ณต์œ /์ €์žฅ ๋ฒ„ํŠผ ์‚ฌ์ด์˜ ๊ฐ„๊ฒฉ์„ ๋Œ€ํญ ์ขํ˜€ ํ•œ๋ˆˆ์— ๋“ค์–ด์˜ค๊ฒŒ ํ–ฅ์ƒ
- **์บ๋ฆญํ„ฐ ์ •๋ณด ์œ„์น˜ ๋ณ€๊ฒฝ**: ๊ฐ•์กฐ๋˜๋Š” ๋ฉ”์ธ ํƒ€์ดํ‹€(ํฌ๊ณ  ๊ตต์€ ํ…์ŠคํŠธ)์—๋Š” ๋‹‰๋„ค์ž„ ๋Œ€์‹  ์บ๋ฆญํ„ฐ ๋ณธ๋ช…(`character.name`)์„ ์œ„์น˜์‹œํ‚ค๊ณ  ๊ทธ ํ•˜๋‹จ์— ์„œ๋ฒ„, ๋‹‰๋„ค์ž„, ์ง์—…์ด ๋ Œ๋”๋ง๋˜๋„๋ก ์Šค์™‘
- **์นด๋“œ ๋ฐฐ๊ฒฝ ํ†ค์•ค๋งค๋„ˆ ์ผ์น˜**: ๋”ฑ๋”ฑํ•œ ๋ฒ ์ด์ง€ ๋‹จ์ƒ‰์—์„œ ๋ฒ—์–ด๋‚˜, ๋ฉ”์ธ ํ™”๋ฉด์˜ ๋ฉ”์ธ ํ…Œ๋งˆ์™€ ๋™์ผํ•œ ๋ถ€๋“œ๋Ÿฌ์šด ์ฝ”๋ž„-์˜ค๋ Œ์ง€ ๊ทธ๋ผ๋ฐ์ด์…˜ ์ ์šฉ (`#FAC486`, `#F2A372`)
- **๋ฒ„ํŠผ ๋ฐ ๊ทธ๋ฃนํ•‘ ๋ถ€๋ชจ ๋ ˆ์ด์•„์›ƒ ์กฐ์ •**: Flexbox(`flex-col`) ์ปจํ…Œ์ด๋„ˆ๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด '์ €์žฅํ•˜๊ธฐ' ๋ฒ„ํŠผ์ด ์„ธ๋กœ๊ฐ€ ๊ธด ๋””๋ฐ”์ด์Šค์—์„œ ํ™”๋ฉด ๋ฐ‘๋ฐ”๋‹ฅ์œผ๋กœ ๋„๋ง๊ฐ€๋Š” ํ˜„์ƒ ์ œ๊ฑฐ ๋ฐ ์นด๋“œ ์ค‘์•™ ์ •๋ ฌ ๊ณ ์ •
- **๋กœ๊ทธ์ธ ์‹œ ์ด๋ฆ„ ์ €์žฅ ๊ธฐ๋Šฅ ๋ณต๊ตฌ**: ์ฒดํฌ๋ฐ•์Šค(`saveName`) UI๋งŒ ์žˆ๋˜ ๊ฒƒ์„ ์‹ค์ œ LocalStorage์™€ ์—ฐ๋™ํ•ด ์ด๋ฆ„ ์ž…๋ ฅ ์ƒํƒœ๋ฅผ ์˜๊ตฌ ์ €์žฅํ•˜๊ณ , ๋‹ค์Œ ๋ฒˆ ํŒ์—… ๋…ธ์ถœ ์‹œ "์ด๋ฆ„ ์ž๋™ ๋ณต์› & ์ฒดํฌ๋ฐ•์Šค ์œ ์ง€ ์ƒํƒœ"๋กœ ์Šคํ† ๋ฆฌ์ง€ ํ•ธ๋“ค๋ง ํ”ฝ์Šค
- **๋กœ๊ทธ์•„์›ƒ ํ”Œ๋กœ์šฐ ํ†ต์ผ์„ฑ ๋ณด์™„**: ์‚ฌ์ด๋“œ๋ฐ”(ํ–„๋ฒ„๊ฑฐ ๋ฉ”๋‰ด) ๋ฐ ํ†ก ์ƒ์„ธ์˜ ์šฐ์ธก ์ƒ๋‹จ ๋กœ๊ทธ์•„์›ƒ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ, ๋กœ๊ทธ์ธ ์ฐฝ์ด ์•„๋‹Œ ๋ฉ”์ธ ๋ฃจํŠธ(`/`)๋กœ ๋ฐ”๋กœ ๋ผ์šฐํŒ…๋˜๋„๋ก ์ •์ •
- **ํ†ก ์•„์ดํ…œ ์‹œ์ธ์„ฑ ๊ฐ•ํ™”**: ํ†ก ๋‚ด์˜ ์œ ์ € ๋„ค์ž„๊ณผ ํ…์ŠคํŠธ ๋ณธ๋ฌธ ๋‘๊ป˜๋ฅผ ๋ชจ๋‘ `font-bold` ๊ธ‰์œผ๋กœ ๊ฐ€์žฅ ์ง„ํ•˜๊ฒŒ ๋ณผ๋“œ ์ฒ˜๋ฆฌ
- **์ƒ์„ธ ํŽ˜์ด์ง€ ํƒ‘๋ฐ”(Header) ๋†’์ด ์ •๋ ฌ**: ๋‹ค๋ฅธ ํŽ˜์ด์ง€ ํƒ‘๋ฐ”์™€ ๋‹ฌ๋ฆฌ `detail` ํ…œํ”Œ๋ฆฟ๋งŒ ํฌ๊ณ  ๋„“์—ˆ๋˜ ํŒจ๋”ฉ ์—ฌ๋ฐฑ ๊ธฐ์ค€(`py-3` -> `py-2`)์„ ํ†ต์ผ ์ ์šฉ
- **์šด์˜ํŒ€ ํ•œ๋งˆ๋”” ์ƒ์„ธ UI ๋™๊ธฐํ™”**: `team-message` ์ƒ์„ธ ํŽ˜์ด์ง€ ๋ ˆ์ด์•„์›ƒ์„ ๊ธฐ์กด ๊ฒฐ์‚ฐ ์ƒ์„ธ(`msg`) ํŽ˜์ด์ง€์™€ ์™„๋ฒฝํžˆ ํ†ต์ผ (ํฐ์ƒ‰ ์ปจํ…Œ์ด๋„ˆ ๋ฐฐ๊ฒฝ ๋ถ„๋ฆฌ, ํ•˜๋‹จ๋ถ€ ๊ฐ ์„น์…˜๋ณ„ ๊ฐ€๋กœ ๊ตฌ๋ถ„์„  ์ถ”๊ฐ€, ํŒจ๋”ฉ ๋ฐ ์—ฌ๋ฐฑ ์ˆ˜์น˜ ์ผ์น˜)
- **์‚ฌ์ด๋“œ๋ฐ” ํ‘ธํ„ฐ ๋””์ž์ธ ๊ฐœํŽธ**: ๊ธฐ์กด "๋ฉ”์ƒ๊ฒฐ์‚ฐ ๊ธฐ๋ก" ํƒ€์ดํ‹€ ํ…์ŠคํŠธ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ , ํ•˜๋‹จ์— ๋ณ„ ๋ชจ์–‘ ์•„์ด์ฝ˜(`Simbol Logo`)๊ณผ ์ „์ฒด ๊ธฐ๋ก ๊ธฐ๊ฐ„ ํ…์ŠคํŠธ๋งŒ ๋ณ‘๋ ฌ ๋ฐฐ์น˜ํ•˜์—ฌ ์‹ฌํ”Œํ•˜๊ฒŒ ๊ฐœ์„ 
- **๋กœ๊ทธ์ธ ํŒ์—… UI ๊ฐœ์„ **: ์ด๋ฆ„ ์ž…๋ ฅ ํผ ๋‚ด ๋ถˆํ•„์š”ํ•œ ๋งž์ถค๋ฒ• ๊ฒ€์‚ฌ(๋นจ๊ฐ„ ์ค„) ๋ฐ ์›น ๋ธŒ๋ผ์šฐ์ € ์ž๋™์™„์„ฑ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋‹จํ’๋ฐ”๋žŒ `InputBox` ์ปดํฌ๋„ŒํŠธ์— `spellcheck="false"`, `autocomplete="off"` ์ผ๊ด„ ์ ์šฉ
- **์šด์˜ํŒ€ ์ „์šฉ ๋ผ๋ฒจ๋ง ํ•„ํ„ฐ๋ง**: `member` ์บ๋ฆญํ„ฐ ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ ์บ๋ฆญํ„ฐ๊ฐ€ '์šด์˜ํŒ€'์ผ ๊ฒฝ์šฐ ๊ธฐ์กด์˜ "๋ ˆ๋ฒจ/์„œ๋ฒ„/์ง์—…" ์ž๋ฆฌ๊ฐ€ ์•„๋‹Œ **"13๊ธฐ / ๊ฐ€์ฒœ๋Œ€ํ•™๊ต / ์šด์˜ํŒ€"**์œผ๋กœ ๊ฐ•์ œ ํ‘œ์‹œ๋˜๋„๋ก ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ ๊ฐœ์„ 
- **๋ฉ”์ƒ๊ฒฐ์‚ฐ ํ†ก ๋””์ž์ธ ๊ฐœ์„ **: ํ†ก ํ•˜๋‹จ์˜ ์ „์†ก ๋ฒ„ํŠผ ํ™”์‚ดํ‘œ ์•„์ด์ฝ˜์„ ํฐ์ƒ‰ ํˆฌ๋ช… ๋ฒ„์ „(`Send, Color=White copy.svg`) ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ๊ต์ฒดํ•˜์—ฌ ๋ฐฐ๊ฒฝ ํ†ค์˜ค๋ฒ„ ๋ฐฉ์ง€ ๋ฐ ์‹œ์ธ์„ฑ ํ™•๋ณด
- **๋ฉ”์ธ ์บ๋ฆญํ„ฐ ์นด๋“œ ๋น„์œจ ์™„๋ฒฝ ๋ณต๊ตฌ**: ์ด์ „ ๋ณ‘ํ•ฉ ๋ฐ ๊ตฌ์กฐ ๋ณ€๊ฒฝ ๊ณผ์ •์—์„œ ํ›ผ์†๋๋˜ ์›๋ž˜ ์บ๋ฆญํ„ฐ ์„œํƒ ๋”ฉ ์ด๋ฏธ์ง€ CSS ๋น„์œจ ์†์„ฑ(`h-40 w-3/4 aspect-[5/6] object-cover`)๋“ค์„ ์›๋ž˜ ์ปค๋ฐ‹๋Œ€๋กœ ๋˜๋Œ๋ฆฌ๊ณ , ๊ฐ€์žฅ์ž๋ฆฌ๊ฐ€ ์„ ๋ช…ํ•˜๊ฒŒ ๋ Œ๋”๋ง๋˜๋„๋ก `image-rendering: pixelated`๋งŒ ํ•จ๊ป˜ ์ ์šฉ

## ๐ŸŽฏ ๊ด€๋ จ ์ด์Šˆ
Closes #(์ด์Šˆ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”)
- ์—†์Œ

## ๐Ÿค– ์‚ฌ์šฉํ•œ Prompt
- "์บ๋ฆญํ„ฐ ์ด๋ฏธ์ง€ ์ž๋ฅด์ง€ ๋ง๊ณ  ์›๋ณธ ์ข…ํšก๋น„ ์œ ์ง€ํ•ด์„œ ์„ธ๋กœ ๋Š˜๋ ค์ค˜, ํ•˜๋‹จ๋งŒ 1/24 ๋‹จ์œ„๋กœ ์ž˜๋ผ"
- "์ €์žฅ ์นด๋“œ๋ฅผ ์ „์ฒด์ ์œผ๋กœ 4/5๋กœ ์ค„์—ฌ์ค˜, ๋ชจ๋ฐ”์ผ์—์„œ ์ €์žฅ ๋ฒ„ํŠผ์ด ์•ˆ ๋ณด์—ฌ"
- "๋ฐฐ๊ฒฝ ๋ฒ ์ด์ง€ ๋‹จ์ƒ‰ ๋ง๊ณ  ๋ฉ”์ธ ํ™”๋ฉด ๊ทธ๋ผ๋ฐ์ด์…˜ ๋ฐฐ๊ฒฝ์œผ๋กœ ๋ณ€๊ฒฝํ•ด"
- "๋ฒ„ํŠผ์ด๋ž‘ ์นด๋“œ ์‚ฌ์ด ๊ฐญ ์ค„์ด๊ณ , ๋ฒ„ํŠผ์ด๋ž‘ ์นด๋“œ๋ž‘ ๋ฌถ์ธ ๋ถ€๋ชจ๋ฅผ ๋งŒ๋“ค์–ด๋†”์„œ ํ•ด์ƒ๋„๊ฐ€ ์„ธ๋กœ๋กœ ๋งค์šฐ ๊ธธ์–ด์ ธ๋„ ๋ฒ„ํŠผ์ด ๊ฐ€์šด๋ฐ ๊ทธ๋ฃน์œผ๋กœ ๋”ฐ๋ผ์˜ค๊ฒŒ ํ•ด"
- "์บ๋ฆญํ„ฐ ์นด๋“œ ์•ˆ์— ์บ๋ฆญํ„ฐ ๋ณธ๋ช…, ๋‹‰๋„ค์ž„ ์ฐธ์กฐํ•˜๋Š” ์• ๋“ค ์„œ๋กœ ์œ„์น˜ ๋ฐ”๊ฟ”์„œ ๊ฐ•์กฐํ•ด ์ค˜"
- "ํฐํŠธ ๋ณผ๋“œ ์ฒ˜๋ฆฌํ•ด ์ค˜"
- "๋กœ๊ทธ์ธ ๋ชจ๋‹ฌ์ฐฝ ์ด๋ฆ„ ์ฒดํฌ๋ฐ•์Šค ๋ˆŒ๋Ÿฌ๋„ ์ €์žฅ ์•ˆ ๋˜๋˜ ๋กœ์ง ์ˆ˜์ •, ์ฒดํฌ ํ’€๋ฆผ ๋ฒ„๊ทธ ํ”ฝ์Šค ๋ฐ ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ์œ ์ง€๋˜๊ฒŒ ํ•ด ์ค˜"
- "์ƒ์„ธ ํŽ˜์ด์ง€ ํ—ค๋” ํƒ‘๋ฐ” ์‚ฌ์ด์ฆˆ๋ฅผ ๋ฉ”์ธ ํ™”๋ฉด ๊บผ๋ž‘ ๋งž์ถฐ ์ค˜"
- "๋กœ๊ทธ์•„์›ƒ ํ•˜๋ฉด ์›๋ž˜ ์žˆ๋˜ /login ํ”Œ๋กœ์šฐ ๋ฒ„๋ฆฌ๊ณ  ๋ฐ”๋กœ ๋ฉ”์ธ /๊ฒฝ๋กœ๋กœ ๊ฐ€๊ฒŒ ํ•ด"
- "team-message/[id] ํŽ˜์ด์ง€์˜ UI๋ฅผ ๊ธฐ์ค€์ด ๋˜๋Š” msg/[id] ํŽ˜์ด์ง€์˜ ๋””์ž์ธ ์š”์†Œ์™€ ์™„๋ฒฝํ•˜๊ฒŒ ํ†ต์ผํ•˜๋„๋ก ์ˆ˜์ •ํ•ด๋ผ. ์ปจํ…Œ์ด๋„ˆ ํฐ๋ฐฐ๊ฒฝ ๋‚˜๋ˆ„๊ธฐ, hr ๊ตฌ๋ถ„์„  ์—ฌ๋ฐฑ ๋“ฑ"
- "๋ฉ”์ธ ํ™”๋ฉด ์บ๋ฆญํ„ฐ ์ด๋ฏธ์ง€๋“ค์˜ ํฌ๊ธฐ๊ฐ€ ์ž‘์•„์ง€๊ณ  ๋ฐฐ์น˜๊ฐ€ ์–ด๊ธ‹๋‚˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ์›๋ณธ ์ฝ”๋“œ ๊ตฌ์กฐ ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ์™„๋ฒฝํžˆ ํ†ต์ผ์‹œํ‚ค๊ณ  ํ”ฝ์…€ํ™” ์œ ์ง€ํ•ด"
- "์šด์˜ํŒ€ ํ•œ๋งˆ๋””๋ฅผ ํ•„ํ„ฐ๋ง ํ•ด์„œ '๋ ˆ๋ฒจ/์„œ๋ฒ„/์ง์—…' ์ž๋ฆฌ์— '13๊ธฐ/๊ฐ€์ฒœ๋Œ€ํ•™๊ต/์šด์˜ํŒ€'์œผ๋กœ ํ‘œ์‹œ๋˜๊ฒŒ ํ•ด์ค˜."
- "์‚ฌ์ด๋“œ๋ฐ” ํ•˜๋‹จ '๋ฉ”์ƒ๊ฒฐ์‚ฐ ๊ธฐ๋ก' ํ…์ŠคํŠธ ์ œ๊ฑฐํ•˜๊ณ  ์‹ฌ๋ณผ ๋กœ๊ณ  svg๋ž‘ ๋‚ ์งœ ๊ธฐ๊ฐ„ ๋ฐฐ์น˜ํ•ด ์ค˜"
- "๋กœ๊ทธ์ธ ํŒ์—…์˜ ์ด๋ฆ„ ์ž…๋ ฅ ์นธ์— ํ…์ŠคํŠธ ์ž…๋ ฅํ•˜๋ฉด ๋นจ๊ฐ„ ์ค„ ์ณ์ง€๋Š” ๊ฑฐ ์—†์•จ ์ˆ˜ ์žˆ์–ด? autocomplete ๊บผ ์ค˜"
- "๋ฉ”์ƒ๊ฒฐ์‚ฐ ํ†ก์˜ ์ „์†ก ๋ฒ„ํŠผ ์•„์ด์ฝ˜ ์ปฌ๋Ÿฌ ํ™”์ดํŠธ ์‚ฌ๋ณธ ์Šค๋‹ˆํŽซ์œผ๋กœ ๋ณ€๊ฒฝํ•ด ์ค˜ (`send-icon-white.svg`)"

## โœ… ์ฒดํฌ๋ฆฌ์ŠคํŠธ
- [x] ๋กœ์ปฌ์—์„œ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ
- [x] ํƒ€์ž… ์—๋Ÿฌ ์—†์Œ (`npm run check`)
- [x] Linter ํ†ต๊ณผ
- [x] ๋ชจ๋ฐ”์ผ ๋ฐ˜์‘ํ˜• ํ™•์ธ
- [ ] ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์š”์ฒญ ์™„๋ฃŒ
- [x] ๋กœ์ปฌ์—์„œ ๊ฐœ๋ฐœํ™˜๊ฒฝ ์ž‘๋™ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ (npm run dev)
- [x] ํƒ€์ž… ๋ฐ ์ถฉ๋Œ ์—๋Ÿฌ ์—†์Œ (`npm run check`, FastAPI ๋ฐฑ์—”๋“œ ๋ฆฐํ„ฐ ํ†ต๊ณผ)
- [x] UI/UX ๋ Œ๋”๋ง ํ™•์ธ (Pixelated, Overflow ๋“ฑ)
- [ ] ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์š”์ฒญ ๋ฐ dev ๋ธŒ๋žœ์น˜ ๋ณ‘ํ•ฉ

## ๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท (์„ ํƒ)
<!-- UI ๋ณ€๊ฒฝ ์ „/ํ›„ ํ˜น์€ ๊ตฌํ˜„ ์™„๋ฃŒ๋œ ํ†ก ํŽ˜์ด์ง€/๋ ˆ์ด์•„์›ƒ ์Šคํฌ๋ฆฐ์ƒท์„ ์ฒจ๋ถ€ํ•ด์ฃผ์„ธ์š” -->


<!-- UI ๋ณ€๊ฒฝ ์ „/ํ›„ ์บก์ฒ˜ ํ™”๋ฉด ํ˜น์€ ์ปดํฌ๋„ŒํŠธ ์ด๋ฏธ์ง€๋ฅผ ์ฒจ๋ถ€ํ•ด์ฃผ์„ธ์š” -->

## ๐Ÿ’ฌ ํŠน์ด์‚ฌํ•ญ
- ์นด๋“œ๊ฐ€ DOM ์ž์ฒด์—์„œ ํฌ๊ธฐ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐ˜์‘ํ˜• ์กฐ์ •๋˜๋„๋ก `clientHeight`, `margin-bottom` ์‚ฐ์ˆ  ๋กœ์ง์ด ๋„์ž…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
- ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ด€๋ จ Python Pillow ์ด๋ฏธ์ง€ ์ตœ์ ํ™”๋ฅผ ์‹œ๋„ํ•˜๋ ค ํ–ˆ์œผ๋‚˜ ๋กœ์ปฌ(UV) ํ™˜๊ฒฝ ๋ฌธ์ œ๋กœ ์ƒ๋žต๋˜์—ˆ์Šต๋‹ˆ๋‹ค. `html-to-image` ์˜ต์…˜ ๋ฐฐ์œจ(`pixelRatio: 3`)์„ ๋Œ€ํญ ๋†’์—ฌ ํ™”์งˆ ์ €ํ•˜ ๋ฌธ์ œ๋ฅผ ํ•ด์†Œํ–ˆ์Šต๋‹ˆ๋‹ค.
- ์ž„์‹œ ๋””๋ฒ„๊น… ์šฉ๋„๋กœ ๊ธฐ์žฌํ•ด ๋‘” ํ•˜๋“œ์ฝ”๋”ฉ ๊ฐ’(๊น€๋‹จ๋ฐ”, ํ•™๋ฒˆ ๋“ฑ) ์ฝ”๋“œ๋ฅผ ์ •๋ฆฌํ•˜๊ณ  ๋ชจ๋‘ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์ดˆ๊ธฐํ™”(`""`) ๋ฐ ์‚ญ์ œํ•˜์˜€์Šต๋‹ˆ๋‹ค.

---

Expand Down
Binary file added dpbr_front/app/check_output.txt
Binary file not shown.
45 changes: 31 additions & 14 deletions dpbr_front/app/src/app.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>๋‹จํ’๋ฐ”๋žŒ</title>
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/images/icons/MSGS_Favicon.ico" />
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
<link rel="stylesheet" as="style" crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>๋‹จํ’๋ฐ”๋žŒ</title>

<!-- SEO & Metadata -->
<meta name="description" content="๋‹จํ’๋ฐ”๋žŒ 13๊ธฐ ๋ฉ”์ƒ๊ฒฐ์‚ฐ - ๋‹น์‹ ์˜ ๋ฉ”์ดํ”Œ ์—ฌ์ •์„ ๊ธฐ๋กํ•˜์„ธ์š”." />
<meta property="og:type" content="website" />
<meta property="og:title" content="๋‹จํ’๋ฐ”๋žŒ" />
<meta property="og:description" content="๋‹จํ’๋ฐ”๋žŒ 13๊ธฐ ๋ฉ”์ƒ๊ฒฐ์‚ฐ - ๋‹น์‹ ์˜ ๋ฉ”์ดํ”Œ ์—ฌ์ •์„ ๊ธฐ๋กํ•˜์„ธ์š”." />
<meta property="og:image" content="%sveltekit.assets%/images/thumbnail.png" />
<meta property="og:url" content="https://gc-maplewind.github.io/MSGS_13_F/" />

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="๋‹จํ’๋ฐ”๋žŒ" />
<meta name="twitter:description" content="๋‹จํ’๋ฐ”๋žŒ 13๊ธฐ ๋ฉ”์ƒ๊ฒฐ์‚ฐ - ๋‹น์‹ ์˜ ๋ฉ”์ดํ”Œ ์—ฌ์ •์„ ๊ธฐ๋กํ•˜์„ธ์š”." />
<meta name="twitter:image" content="%sveltekit.assets%/images/thumbnail.png" />

%sveltekit.head%
</head>

<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>

</html>
45 changes: 32 additions & 13 deletions dpbr_front/app/src/lib/components/BottomSheetLogin.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
let studentIdInputRef: HTMLDivElement | undefined = $state();
let dialogEl: HTMLDivElement | undefined = $state();

// ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ƒํƒœ
let errorMessage = $state("");

// ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์œ„ํ•œ ์ƒํƒœ
let isVisible = $state(false);

Expand Down Expand Up @@ -106,7 +109,10 @@
*/

function showToastMessage(message?: string) {
toast.show(message || "์ด๋ฆ„ ๋˜๋Š” ํ•™๋ฒˆ์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.");
errorMessage = message || "์ด๋ฆ„ ๋˜๋Š” ํ•™๋ฒˆ์„ ํ™•์ธํ•ด ์ฃผ์„ธ์š”.";
setTimeout(() => {
errorMessage = "";
}, 3000);
}

// Focus/Blur ํ•ธ๋“ค๋Ÿฌ๋“ค
Expand Down Expand Up @@ -158,7 +164,7 @@
tabindex="-1"
>
<div
class="w-full h-[72%] bg-gradient-to-b from-[#FCDDA5] to-[#F1A470] rounded-t-3xl pt-4 pb-8 px-6 flex flex-col items-center shadow-lg transition-transform duration-300 {isVisible
class="w-full shrink-0 h-[72vh] bg-gradient-to-b from-[#FCDDA5] to-[#F1A470] rounded-t-3xl pt-4 pb-8 px-6 flex flex-col items-center shadow-lg transition-transform duration-300 {isVisible
? 'translate-y-0'
: 'translate-y-full'}"
onclick={(e) => e.stopPropagation()}
Expand All @@ -167,7 +173,7 @@
<div class="w-full flex justify-end">
<button onclick={handleClose} class="text-white p-2">
<img
src="/images/icons/name=Close, Color=White.svg"
src="/images/icons/close-icon-white.svg"
alt="๋‹ซ๊ธฐ"
class="w-6 h-6"
draggable="false"
Expand Down Expand Up @@ -226,14 +232,27 @@
</div>

<!-- ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ -->
<Button
label="๋ฉ”์ƒ๊ฒฐ์‚ฐ ํ†ก ์ž…์žฅ"
variant="primary"
buttonState={isLoading ? "disabled" : "default"}
onClick={handleLogin}
type="button"
class="bg-white !text-[#F87C56] hover:bg-white/90 font-medium py-[14px] rounded-lg"
/>
<div class="relative w-full">
{#if errorMessage}
<div
class="absolute bottom-[calc(100%+8px)] left-0 w-full flex justify-center z-[60] pointer-events-none"
>
<span
class="bg-black/60 text-white text-[15px] px-5 py-2.5 rounded-3xl shadow-md font-medium whitespace-nowrap pointer-events-auto"
>
{errorMessage}
</span>
</div>
{/if}
<Button
label="๋ฉ”์ƒ๊ฒฐ์‚ฐ ํ†ก ์ž…์žฅ"
variant="primary"
buttonState={isLoading ? "disabled" : "default"}
onClick={handleLogin}
type="button"
class="bg-white !text-[#F87C56] hover:bg-white/90 font-medium py-[14px] rounded-lg w-full"
/>
</div>

<!-- ์ด๋ฆ„ ์ €์žฅ ์ฒดํฌ๋ฐ•์Šค -->
<label class="flex items-center gap-2 cursor-pointer mt-2 mb-2">
Expand All @@ -248,14 +267,14 @@
>
{#if saveName}
<img
src="/images/icons/name=check-enable, Color=White.svg"
src="/images/icons/check-enable-icon.svg"
alt="์ €์žฅ ํ™œ์„ฑํ™”"
class="w-full h-full"
draggable="false"
/>
{:else}
<img
src="/images/icons/name=check-disable, Color=White.svg"
src="/images/icons/check-disable-icon.svg"
alt="์ €์žฅ ๋น„ํ™œ์„ฑํ™”"
class="w-full h-full"
draggable="false"
Expand Down
144 changes: 72 additions & 72 deletions dpbr_front/app/src/lib/components/CharacterCard.svelte
Original file line number Diff line number Diff line change
@@ -1,85 +1,85 @@
<script lang="ts">
import type { Character } from "$lib/types";
import { handleImageError } from "$lib/utils/image";
import type { Character } from "$lib/types";
import { handleImageError } from "$lib/utils/image";

interface Props {
character: Character;
}
interface Props {
character: Character;
}

let { character }: Props = $props();
let { character }: Props = $props();

// ์„œ๋ฒ„๋ช… ๋งคํ•‘ (world name to english logo filename)
const worldLogoMap: Record<string, string> = {
์˜ค๋กœ๋ผ: "aurora",
๋ ˆ๋“œ: "red",
์ด๋…ธ์‹œ์Šค: "enosis",
์œ ๋‹ˆ์˜จ: "union",
์Šค์นด๋‹ˆ์•„: "scania",
๋ฃจ๋‚˜: "luna",
์ œ๋‹ˆ์Šค: "zenith",
ํฌ๋กœ์•„: "croa",
๋ฒ ๋ผ: "bera",
์—˜๋ฆฌ์‹œ์›€: "elysium",
์•„์ผ€์ธ: "arcane",
๋…ธ๋ฐ”: "nova",
// ์ผ๋ฐ˜/๋ฆฌ๋ถ€ํŠธ ์•„๋‹Œ ํŠน์ˆ˜ ๊ตฌ๋ณ„์šฉ ๋งตํ•‘ ์ถ”๊ฐ€
๋ฆฌ๋ถ€ํŠธ: "reboot",
๋ฆฌ๋ถ€ํŠธ2: "reboot2",
๋ฒ„๋‹: "burning",
์—์˜ค์Šค: "eos",
ํ—ฌ๋ฆฌ์˜ค์Šค: "helios",
์ฑŒ๋ฆฐ์ €์Šค: "challengers",
};
// ์„œ๋ฒ„๋ช… ๋งคํ•‘ (world name to english logo filename)
const worldLogoMap: Record<string, string> = {
์˜ค๋กœ๋ผ: "aurora",
๋ ˆ๋“œ: "red",
์ด๋…ธ์‹œ์Šค: "enosis",
์œ ๋‹ˆ์˜จ: "union",
์Šค์นด๋‹ˆ์•„: "scania",
๋ฃจ๋‚˜: "luna",
์ œ๋‹ˆ์Šค: "zenith",
ํฌ๋กœ์•„: "croa",
๋ฒ ๋ผ: "bera",
์—˜๋ฆฌ์‹œ์›€: "elysium",
์•„์ผ€์ธ: "arcane",
๋…ธ๋ฐ”: "nova",
// ์ผ๋ฐ˜/๋ฆฌ๋ถ€ํŠธ ์•„๋‹Œ ํŠน์ˆ˜ ๊ตฌ๋ณ„์šฉ ๋งตํ•‘ ์ถ”๊ฐ€
๋ฆฌ๋ถ€ํŠธ: "reboot",
๋ฆฌ๋ถ€ํŠธ2: "reboot2",
๋ฒ„๋‹: "burning",
์—์˜ค์Šค: "eos",
ํ—ฌ๋ฆฌ์˜ค์Šค: "helios",
์ฑŒ๋ฆฐ์ €์Šค: "challengers",
};

function getWorldLogoPath(serverName: string): string {
const englishName = worldLogoMap[serverName];
if (!englishName) return ""; // ๋งคํ•‘๋˜์ง€ ์•Š์€ ์„œ๋ฒ„๋Š” ๋นˆ์นธ ์œ ์ง€ (fallback)
return `/images/worlds/${englishName}.png`;
}
function getWorldLogoPath(serverName: string): string {
const englishName = worldLogoMap[serverName];
if (!englishName) return ""; // ๋งคํ•‘๋˜์ง€ ์•Š์€ ์„œ๋ฒ„๋Š” ๋นˆ์นธ ์œ ์ง€ (fallback)
return `/images/worlds/${englishName}.png`;
}

let logoPath = $derived(getWorldLogoPath(character.server));
let isWorldLogoLoadFailed = $state(false);
let logoPath = $derived(getWorldLogoPath(character.server));
let isWorldLogoLoadFailed = $state(false);
</script>

<a
href="/member/{character.id}"
class="flex flex-col bg-white rounded-lg px-3 py-2 hover:shadow-md transition-shadow"
href="/member/{character.id}"
class="flex flex-col bg-white rounded-lg px-3 py-2 hover:shadow-md transition-shadow"
>
<!-- Badge area (World Logo) -->
<div class="h-4 flex items-center mb-1">
{#if logoPath && !isWorldLogoLoadFailed}
<img
src={logoPath}
alt={character.server}
class="h-full object-contain"
onerror={() => {
isWorldLogoLoadFailed = true;
}}
/>
{:else}
<span class="text-[10px] text-text-muted bg-bg-light px-1 rounded"
>{character.server}</span
>
{/if}
</div>
<!-- Badge area (World Logo) -->
<div class="h-4 flex items-center mb-1">
{#if logoPath && !isWorldLogoLoadFailed}
<img
src={logoPath}
alt={character.server}
class="h-full object-contain"
onerror={() => {
isWorldLogoLoadFailed = true;
}}
/>
{:else}
<span class="text-[10px] text-text-muted bg-bg-light px-1 rounded"
>{character.server}</span
>
{/if}
</div>

<!-- Avatar -->
<div class="flex justify-center items-center py-2">
<img
src={character.avatarUrl}
alt={character.name}
onerror={handleImageError}
class="w-full aspect-square object-contain"
/>
</div>
<!-- Avatar -->
<div class="flex justify-center items-center py-2">
<img
src={character.avatarUrl}
alt={character.name}
onerror={handleImageError}
class="w-full aspect-square object-contain"
/>
</div>

<!-- Info -->
<div class="flex flex-col">
<span class="text-sm text-text-primary leading-tight"
>{character.name}</span
>
<span class="text-xs text-primary-dark leading-tight"
>{character.nickname}</span
>
</div>
<!-- Info -->
<div class="flex flex-col">
<span class="text-sm text-text-primary leading-tight"
>{character.name}</span
>
<span class="text-xs text-primary-dark leading-tight"
>{character.nickname}</span
>
</div>
</a>
Loading
Loading