style.scss 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. // ─────────────────────────────────────────────────────────────
  2. // /chat — 메인 실시간 채팅 (d2-community §⑤, roomKey=main)
  3. // 색/치수는 app/styles/tokens 변수만 사용 (하드코딩 금지)
  4. // Twitch 스타일: border-radius 0, 선 기반 깔끔한 UI
  5. // 채팅 도크(우측 고정) + 팝업(/chat-popup) 공용 스타일
  6. // ─────────────────────────────────────────────────────────────
  7. #chat-room {
  8. display: flex;
  9. flex-direction: column;
  10. height: 100%;
  11. min-height: 0;
  12. padding: var(--sp-4) var(--sp-5);
  13. gap: var(--sp-3);
  14. @media (max-width: 767.98px) {
  15. padding: var(--sp-3);
  16. }
  17. // ── 헤더: 참여자 수(클릭→목록) + 상태 + 컨트롤 ──
  18. .chat-room__head {
  19. display: flex;
  20. align-items: center;
  21. justify-content: space-between;
  22. flex-wrap: wrap;
  23. gap: var(--sp-2);
  24. }
  25. // 참여자 수 버튼 (실시간 채팅 label 위치)
  26. .chat-room__count {
  27. display: inline-flex;
  28. align-items: center;
  29. gap: var(--sp-2);
  30. padding: var(--sp-1) var(--sp-2);
  31. background: transparent;
  32. border: none;
  33. cursor: pointer;
  34. font-size: var(--fs-sm);
  35. color: var(--text-secondary);
  36. font-variant-numeric: tabular-nums;
  37. &:hover {
  38. background: var(--bg-icon);
  39. color: var(--text-primary);
  40. }
  41. > svg:first-child {
  42. color: var(--text-muted);
  43. }
  44. }
  45. .chat-room__count-num {
  46. font-weight: 700;
  47. color: var(--text-primary);
  48. }
  49. .chat-room__count-caret {
  50. font-size: var(--fs-2xs);
  51. color: var(--text-muted);
  52. transition: transform 0.15s ease;
  53. &.is-open {
  54. transform: rotate(180deg);
  55. }
  56. }
  57. .chat-room__meta {
  58. display: flex;
  59. align-items: center;
  60. gap: var(--sp-2);
  61. font-size: var(--fs-sm);
  62. color: var(--text-secondary);
  63. }
  64. .chat-room__state {
  65. color: var(--text-muted);
  66. white-space: nowrap;
  67. &--warn {
  68. color: var(--az-accent);
  69. }
  70. &--error {
  71. color: var(--color-danger);
  72. }
  73. }
  74. .chat-room__notice {
  75. display: flex;
  76. align-items: center;
  77. gap: var(--sp-3);
  78. padding: var(--sp-3) var(--sp-4);
  79. background: var(--bg-subtle);
  80. border: 1px solid var(--border-default);
  81. border-radius: 0;
  82. font-size: var(--fs-sm);
  83. color: var(--text-secondary);
  84. svg {
  85. flex-shrink: 0;
  86. color: var(--az-accent);
  87. }
  88. }
  89. .chat-room__body {
  90. position: relative;
  91. display: flex;
  92. flex: 1;
  93. min-height: 0;
  94. }
  95. .chat-room__messages {
  96. flex: 1;
  97. min-height: 0;
  98. overflow-y: auto;
  99. padding: var(--sp-3);
  100. background: var(--bg-elevated);
  101. border: 1px solid var(--border-default);
  102. border-radius: 0;
  103. }
  104. .chat-room__empty {
  105. padding: var(--sp-6) var(--sp-4);
  106. text-align: center;
  107. font-size: var(--fs-sm);
  108. color: var(--text-muted);
  109. }
  110. .chat-room__message {
  111. display: flex;
  112. align-items: baseline;
  113. gap: var(--sp-3);
  114. padding: var(--sp-2) var(--sp-3);
  115. // 글씨 크게/작게 — --chat-fs 배율 (이름·내용 상속)
  116. font-size: calc(var(--fs-base) * var(--chat-fs, 1));
  117. line-height: 1.5;
  118. border-radius: 0;
  119. &:hover {
  120. background: var(--bg-subtle-hover);
  121. }
  122. }
  123. .chat-room__time {
  124. flex-shrink: 0;
  125. font-size: var(--fs-2xs);
  126. color: var(--text-muted);
  127. font-variant-numeric: tabular-nums;
  128. }
  129. .chat-room__badge {
  130. flex-shrink: 0;
  131. align-self: center;
  132. width: var(--sp-5);
  133. height: var(--sp-5);
  134. object-fit: contain;
  135. }
  136. .chat-room__name {
  137. flex-shrink: 0;
  138. font-weight: 600;
  139. color: var(--title-color, var(--text-secondary));
  140. }
  141. .chat-room__content {
  142. color: var(--text-primary);
  143. word-break: break-word;
  144. overflow-wrap: anywhere;
  145. }
  146. // 시스템 메시지 — 일반 메시지와 스타일 구분
  147. .chat-room__system {
  148. padding: var(--sp-2) var(--sp-3);
  149. text-align: center;
  150. font-size: var(--fs-xs);
  151. font-style: italic;
  152. color: var(--text-muted);
  153. }
  154. // 참여자 목록 오버레이 — 본체(.chat-room__body) 를 덮음
  155. .chat-room__participants {
  156. position: absolute;
  157. inset: 0;
  158. z-index: 20;
  159. display: flex;
  160. }
  161. // "새 메시지 ↓" 점프 버튼 — 위로 스크롤 중 새 메시지 도착 시 노출
  162. .chat-room__jump {
  163. position: absolute;
  164. bottom: var(--sp-4);
  165. left: 50%;
  166. transform: translateX(-50%);
  167. display: inline-flex;
  168. align-items: center;
  169. gap: var(--sp-2);
  170. padding: var(--sp-2) var(--sp-4);
  171. background: var(--az-accent);
  172. color: var(--btn-primary-text);
  173. font-size: var(--fs-sm);
  174. font-weight: 600;
  175. border: none;
  176. border-radius: 0;
  177. cursor: pointer;
  178. box-shadow: 0 var(--sp-1) var(--sp-3) var(--shadow-color);
  179. &:hover {
  180. background: var(--az-accent-hover);
  181. }
  182. }
  183. .chat-room__footer {
  184. position: relative;
  185. display: flex;
  186. flex-direction: column;
  187. gap: var(--sp-2);
  188. }
  189. .chat-room__input-row {
  190. display: flex;
  191. align-items: stretch;
  192. gap: var(--sp-2);
  193. }
  194. .chat-room__input {
  195. flex: 1;
  196. min-width: 0;
  197. padding: var(--sp-3) var(--sp-4);
  198. background: var(--bg-input);
  199. color: var(--text-primary);
  200. font-size: var(--fs-base);
  201. border: 1px solid var(--border-strong);
  202. border-radius: 0;
  203. &::placeholder {
  204. color: var(--text-muted);
  205. }
  206. &:disabled {
  207. opacity: 0.6;
  208. cursor: not-allowed;
  209. }
  210. }
  211. .chat-room__send {
  212. flex-shrink: 0;
  213. display: inline-flex;
  214. align-items: center;
  215. justify-content: center;
  216. min-width: var(--sp-7);
  217. padding: 0 var(--sp-4);
  218. background: var(--az-accent);
  219. color: var(--btn-primary-text);
  220. font-size: var(--fs-base);
  221. font-weight: 600;
  222. border: none;
  223. border-radius: 0;
  224. cursor: pointer;
  225. &:hover:not(:disabled) {
  226. background: var(--az-accent-hover);
  227. }
  228. &:disabled {
  229. background: var(--bg-subtle);
  230. color: var(--text-muted);
  231. cursor: not-allowed;
  232. }
  233. }
  234. .chat-room__cooldown {
  235. font-variant-numeric: tabular-nums;
  236. }
  237. // 푸터 이모지 버튼 — 입력창·전송과 높이 정렬(선 기반 아이콘 버튼)
  238. #emojiPicker {
  239. flex-shrink: 0;
  240. position: static; // 팝업(aside)의 위치 기준을 .chat-room__footer 로 이동
  241. > button {
  242. display: inline-flex;
  243. align-items: center;
  244. justify-content: center;
  245. width: var(--sp-7);
  246. height: 100%;
  247. min-height: var(--sp-7);
  248. background: var(--bg-subtle);
  249. border: 1px solid var(--border-strong);
  250. color: var(--text-secondary);
  251. cursor: pointer;
  252. &:hover, &:focus, &:active {
  253. background: var(--bg-subtle-hover);
  254. color: var(--text-link);
  255. }
  256. }
  257. // 좁은 도크 폭 안에서 위로 열림 — footer 폭에 맞춰 span, 화면 밖 클리핑 방지
  258. > aside {
  259. position: absolute;
  260. top: auto;
  261. bottom: calc(100% + var(--sp-2));
  262. left: var(--sp-3);
  263. right: var(--sp-3);
  264. width: auto !important;
  265. height: min(340px, 50vh) !important;
  266. max-width: none;
  267. // Twitch 플랫: 이모지 팝업도 라운드 제거 (라이브러리 기본 8px 상쇄)
  268. border-radius: 0;
  269. --epr-width: auto;
  270. --epr-height: 340px;
  271. --epr-picker-border-radius: 0;
  272. }
  273. }
  274. .chat-room__blocked {
  275. padding: var(--sp-4);
  276. text-align: center;
  277. background: var(--color-danger-bg);
  278. border: 1px solid var(--color-danger-border);
  279. border-radius: 0;
  280. font-size: var(--fs-sm);
  281. color: var(--color-danger);
  282. }
  283. // 팝업(/chat-popup) — 전체 창 채움
  284. &.chat-room--popup {
  285. flex: 1;
  286. min-height: 0;
  287. }
  288. }
  289. // ── 공용 아이콘 버튼 (헤더 수신설정 · 설정 트리거 · 닫기) ──
  290. .chat-room__icon-btn,
  291. .chat-menu__trigger,
  292. .chat-participants__close,
  293. .chat-modal__close {
  294. display: inline-flex;
  295. align-items: center;
  296. justify-content: center;
  297. width: var(--sp-6);
  298. height: var(--sp-6);
  299. background: transparent;
  300. border: none;
  301. cursor: pointer;
  302. color: var(--text-secondary);
  303. font-size: var(--fs-base);
  304. border-radius: 0;
  305. &:hover {
  306. background: var(--bg-icon);
  307. color: #fff; // --bg-icon(다크)은 양 테마 공통 → 라이트에서 --text-primary(어두움)가 안 보이므로 흰색 고정
  308. }
  309. }
  310. // ── 접속 참여자 목록 ──
  311. .chat-participants {
  312. display: flex;
  313. flex: 1;
  314. min-width: 0;
  315. flex-direction: column;
  316. background: var(--bg-elevated);
  317. border: 1px solid var(--border-default);
  318. &__head {
  319. display: flex;
  320. align-items: center;
  321. justify-content: space-between;
  322. padding: var(--sp-3);
  323. border-bottom: 1px solid var(--border-default);
  324. }
  325. &__title {
  326. font-size: var(--fs-sm);
  327. font-weight: 600;
  328. color: var(--text-primary);
  329. }
  330. &__body {
  331. flex: 1;
  332. min-height: 0;
  333. overflow-y: auto;
  334. }
  335. &__empty {
  336. padding: var(--sp-6) var(--sp-4);
  337. text-align: center;
  338. font-size: var(--fs-sm);
  339. color: var(--text-muted);
  340. }
  341. &__list {
  342. display: flex;
  343. flex-direction: column;
  344. }
  345. &__item {
  346. display: flex;
  347. align-items: center;
  348. gap: var(--sp-3);
  349. padding: var(--sp-2) var(--sp-3);
  350. &:hover {
  351. background: var(--bg-subtle-hover);
  352. }
  353. }
  354. &__avatar {
  355. flex-shrink: 0;
  356. display: inline-flex;
  357. align-items: center;
  358. justify-content: center;
  359. width: var(--sp-6);
  360. height: var(--sp-6);
  361. background: var(--bg-subtle);
  362. color: var(--text-muted);
  363. overflow: hidden;
  364. img {
  365. width: 100%;
  366. height: 100%;
  367. object-fit: cover;
  368. }
  369. }
  370. &__info {
  371. display: flex;
  372. flex-direction: column;
  373. flex: 1;
  374. min-width: 0;
  375. }
  376. &__name {
  377. font-size: var(--fs-sm);
  378. font-weight: 600;
  379. color: var(--text-primary);
  380. white-space: nowrap;
  381. overflow: hidden;
  382. text-overflow: ellipsis;
  383. }
  384. &__sid {
  385. font-size: var(--fs-2xs);
  386. color: var(--text-muted);
  387. white-space: nowrap;
  388. overflow: hidden;
  389. text-overflow: ellipsis;
  390. }
  391. &__level {
  392. flex-shrink: 0;
  393. padding: 0 var(--sp-2);
  394. font-size: var(--fs-2xs);
  395. font-weight: 600;
  396. color: var(--az-accent);
  397. border: 1px solid var(--border-default);
  398. }
  399. }
  400. // ── 설정 dropdown (톱니바퀴) ──
  401. .chat-menu {
  402. position: relative;
  403. display: inline-flex;
  404. &__dropdown {
  405. position: absolute;
  406. top: calc(100% + var(--sp-2));
  407. right: 0;
  408. z-index: 30;
  409. min-width: 160px;
  410. padding: var(--sp-2) 0;
  411. background: var(--bg-elevated);
  412. border: 1px solid var(--border-default);
  413. box-shadow: 0 var(--sp-1) var(--sp-3) var(--shadow-color);
  414. }
  415. &__item {
  416. display: flex;
  417. align-items: center;
  418. gap: var(--sp-3);
  419. width: 100%;
  420. padding: var(--sp-2) var(--sp-4);
  421. background: transparent;
  422. border: none;
  423. cursor: pointer;
  424. font-size: var(--fs-sm);
  425. color: var(--text-secondary);
  426. text-align: left;
  427. svg {
  428. width: var(--sp-4);
  429. color: var(--text-muted);
  430. }
  431. &:hover {
  432. background: var(--bg-subtle-hover);
  433. color: var(--text-primary);
  434. svg {
  435. color: var(--az-accent);
  436. }
  437. }
  438. }
  439. &__divider {
  440. height: 1px;
  441. margin: var(--sp-2) 0;
  442. background: var(--border-default);
  443. }
  444. }
  445. // ── 수신설정 modal (portal → body) ──
  446. .chat-modal {
  447. position: fixed;
  448. inset: 0;
  449. z-index: 1200;
  450. display: flex;
  451. align-items: center;
  452. justify-content: center;
  453. padding: var(--sp-4);
  454. background: var(--overlay-color);
  455. &__panel {
  456. display: flex;
  457. flex-direction: column;
  458. width: min(420px, 100%);
  459. max-height: min(560px, calc(100dvh - 2rem));
  460. background: var(--bg-page);
  461. border: 1px solid var(--border-default);
  462. box-shadow: 0 var(--sp-2) var(--sp-5) var(--shadow-color);
  463. }
  464. &__head {
  465. display: flex;
  466. align-items: center;
  467. justify-content: space-between;
  468. padding: var(--sp-4);
  469. border-bottom: 1px solid var(--border-default);
  470. }
  471. &__title {
  472. font-size: var(--fs-lg);
  473. font-weight: 700;
  474. color: var(--text-primary);
  475. }
  476. &__body {
  477. display: flex;
  478. flex-direction: column;
  479. gap: var(--sp-4);
  480. padding: var(--sp-4);
  481. overflow-y: auto;
  482. }
  483. &__loading {
  484. padding: var(--sp-5);
  485. text-align: center;
  486. font-size: var(--fs-sm);
  487. color: var(--text-muted);
  488. }
  489. &__error {
  490. font-size: var(--fs-sm);
  491. color: var(--color-danger);
  492. }
  493. &__foot {
  494. display: flex;
  495. justify-content: flex-end;
  496. gap: var(--sp-3);
  497. padding: var(--sp-4);
  498. border-top: 1px solid var(--border-default);
  499. }
  500. &__btn {
  501. padding: var(--sp-2) var(--sp-5);
  502. font-size: var(--fs-sm);
  503. font-weight: 600;
  504. border: 1px solid transparent;
  505. border-radius: 0;
  506. cursor: pointer;
  507. &--ghost {
  508. background: transparent;
  509. border-color: var(--border-strong);
  510. color: var(--text-secondary);
  511. &:hover:not(:disabled) {
  512. background: var(--bg-subtle-hover);
  513. color: var(--text-primary);
  514. }
  515. }
  516. &--primary {
  517. background: var(--az-accent);
  518. color: var(--btn-primary-text);
  519. &:hover:not(:disabled) {
  520. background: var(--az-accent-hover);
  521. }
  522. }
  523. &:disabled {
  524. opacity: 0.6;
  525. cursor: not-allowed;
  526. }
  527. }
  528. }
  529. // ── 토글 스위치 (초대/귓속말/팔로잉 접속 표시) — 선 기반 사각 스위치 ──
  530. .chat-toggle {
  531. display: flex;
  532. align-items: center;
  533. justify-content: space-between;
  534. gap: var(--sp-4);
  535. cursor: pointer;
  536. &__text {
  537. display: flex;
  538. flex-direction: column;
  539. gap: var(--sp-1);
  540. min-width: 0;
  541. }
  542. &__label {
  543. font-size: var(--fs-base);
  544. font-weight: 600;
  545. color: var(--text-primary);
  546. }
  547. &__desc {
  548. font-size: var(--fs-2xs);
  549. color: var(--text-muted);
  550. }
  551. &__switch {
  552. position: relative;
  553. flex-shrink: 0;
  554. width: 44px;
  555. height: 24px;
  556. input {
  557. position: absolute;
  558. inset: 0;
  559. z-index: 1;
  560. margin: 0;
  561. opacity: 0;
  562. cursor: pointer;
  563. &:disabled {
  564. cursor: not-allowed;
  565. }
  566. }
  567. }
  568. &__slider {
  569. position: absolute;
  570. inset: 0;
  571. background: var(--bg-subtle);
  572. border: 1px solid var(--border-strong);
  573. transition: background 0.15s ease, border-color 0.15s ease;
  574. &::before {
  575. content: '';
  576. position: absolute;
  577. top: 2px;
  578. left: 2px;
  579. width: 18px;
  580. height: 18px;
  581. background: var(--text-muted);
  582. transition: transform 0.15s ease, background 0.15s ease;
  583. }
  584. }
  585. input:checked + &__slider {
  586. background: var(--az-accent);
  587. border-color: var(--az-accent);
  588. &::before {
  589. transform: translateX(20px);
  590. background: var(--btn-primary-text);
  591. }
  592. }
  593. input:focus-visible + &__slider {
  594. outline: 2px solid var(--az-accent);
  595. outline-offset: 2px;
  596. }
  597. }
  598. // ── 채팅 팝업 창(/chat-popup) 래퍼 — 전체 뷰포트 ──
  599. .chat-popup {
  600. display: flex;
  601. height: 100vh;
  602. height: 100dvh;
  603. }