feat(frontend): 전역 셸·로그인·세션 처리와 웹메일 화면

확정된 디자인(docs/design/webmail)을 기준으로 만든 웹메일 화면이다.

- 앞으로 붙일 사내 모듈(캘린더·파일·ERP 등)을 위한 전역 앱 레일과 셸,
  Pretendard 자체 호스팅, 바다 청록 브랜드 토큰과 콘텐츠 열 토큰
- 로그인과 세션 처리: 401 이면 한 번 갱신한 뒤 다시 보내고, 실패하면 세션 만료 대화상자를 띄운다.
  로그아웃은 다른 탭에도 알리고, 로그아웃 뒤에 늦게 온 갱신 결과는 버린다
- 메일함(분할·목록 보기), 읽기(외부 HTML 은 sandbox iframe 으로만 그린다),
  쓰기(발송 키 규칙, 첨부 형식·크기·합계·개수 사전 검사, 답장·전달 초안 길이 맞춤)
- 오류는 코드로만 분기하고, 문구는 생성된 에러 사전에서 가져온다

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 01:31:59 +09:00
parent 71f2e01d83
commit 468a4377ee
104 changed files with 14136 additions and 747 deletions
+72
View File
@@ -0,0 +1,72 @@
/*
* 전역 기본 스타일 — 요소 기본값과 공용 유틸만 둔다.
* 값은 tokens.css 의 변수만 쓴다. 화면별 모양은 각 컴포넌트의 <style scoped> 에 둔다.
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body {
margin: 0;
font-family: var(--font-family-base);
font-size: var(--font-size-base);
line-height: var(--line-height-base);
color: var(--color-text);
background: var(--color-surface);
-webkit-font-smoothing: antialiased;
}
button,
input,
select,
textarea {
font: inherit;
color: inherit;
}
button:not(:disabled) {
cursor: pointer;
}
a {
color: var(--color-brand);
}
a:hover {
color: var(--color-brand-strong);
}
:focus-visible {
outline: var(--focus-ring-width) solid var(--color-brand);
outline-offset: var(--focus-ring-offset);
}
/* 화면에는 숨기고 스크린리더에는 읽힌다 (안 읽음·준비 중 같은 상태 전달용) */
.visually-hidden {
position: absolute;
width: var(--space-1);
height: var(--space-1);
margin: calc(var(--space-1) * -1);
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
+163
View File
@@ -0,0 +1,163 @@
/*
* 디자인 토큰 — 색 · 간격 · 글자 크기는 이 파일 하나에만 둔다.
* 기준: docs/design/webmail (확정본) — 브랜드 "바다 청록" 바탕 #0B3640 · 강조 #0E7C86
* 컴포넌트는 <style scoped> 에서 var(--…) 로만 쓰고, 치수는 rem(16px = 1rem)으로 적는다.
*
* 화면 폭 기준 (CSS 변수는 미디어 쿼리에 쓸 수 없어 값으로 적는다):
* 70rem(1120px) 이상 — 레일 · 메일함 · 목록 · 본문이 한 줄에 놓이고 칸마다 따로 스크롤된다.
* 그보다 좁으면 칸이 아래로 접히고 화면 전체가 스크롤된다.
* 목록 보기 표(MessageTable) — 68.6875rem(1099px) 이하에서 크기 칸 숨김, 37.4375rem(599px) 이하에서 칸 축소.
*/
:root {
/* ── 글꼴 ─────────────────────────────── */
--font-family-base:
'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo',
'Malgun Gothic', system-ui, sans-serif;
--font-size-caption: 0.6875rem; /* 11px — 앱 레일 이름 */
--font-size-small: 0.75rem; /* 12px — 시각·개수·보조 설명 */
--font-size-label: 0.78125rem; /* 12.5px — 입력 이름·안내 */
--font-size-base: 0.8125rem; /* 13px — 기본 */
--font-size-emphasis: 0.84375rem; /* 13.5px — 주요 버튼·보낸 사람 */
--font-size-body: 0.875rem; /* 14px — 본문·입력 */
--font-size-title-sm: 0.9375rem; /* 15px — 영역 제목 */
--font-size-title: 1rem; /* 16px — 상단 바 모듈 이름 */
--font-size-heading: 1.25rem; /* 20px — 메일 제목 */
--font-size-heading-lg: 1.375rem; /* 22px — 로그인 제목 */
--font-size-display: 1.875rem; /* 30px — 로그인 소개 문구 */
--line-height-base: 1.5;
--line-height-body: 1.75;
--line-height-tight: 1.3;
--line-height-heading: 1.4;
--letter-spacing-tight: -0.01em;
--letter-spacing-tighter: -0.015em;
--letter-spacing-heading: -0.02em;
--letter-spacing-display: -0.025em;
/* ── 색: 브랜드 ─────────────────────────── */
--color-brand-surface: #0b3640; /* 레일 · 로그인 패널 · 성공 알림 바탕 */
--color-brand-surface-active: #155a64; /* 레일 현재 모듈 */
--color-brand: #0e7c86; /* 강조 버튼 · 링크 · 진행률 */
--color-brand-strong: #0a5f67; /* 강조 글자 · 눌린 상태 */
--color-brand-subtle: #e3f2f3; /* 선택한 행 · 현재 메일함 · 아바타 */
--color-on-brand: #ffffff;
--color-on-brand-text: #e6eff0;
--color-on-brand-muted: #a3c2c6;
--color-on-brand-link: #9ed6db;
--color-on-brand-success: #9fe0b5;
/* ── 색: 글자 ───────────────────────────── */
--color-text: #171c26;
--color-text-body: #1f2733; /* 메일 본문 */
--color-text-strong: #344054; /* 입력 이름 · 도구 버튼 */
--color-text-secondary: #4a5363;
--color-text-muted: #667085;
--color-icon-muted: #98a2b3;
/* ── 색: 면 · 선 ────────────────────────── */
--color-surface: #ffffff;
--color-surface-panel: #f8f9fb; /* 메일함 패널 */
--color-surface-muted: #f2f4f7; /* 받는 사람 토큰 · 파일 아이콘 */
--color-surface-sunken: #fbfbfc; /* 목록 표 머리 줄 */
--color-row-hover: #f3f8f8; /* 목록 표 행 hover */
--color-border: #e6e8ec;
--color-border-subtle: #eef0f3;
--color-border-strong: #d0d5dd; /* 보기 전환 세그먼트 테두리 */
--color-border-input: #8a94a3;
--color-skeleton: #e4e7ec; /* 불러오는 중 막대 · 진행률 바탕 */
/* ── 색: 상태 ───────────────────────────── */
--color-danger: #b42318;
--color-danger-surface: #fef3f2;
--color-danger-border: #fbd7d3;
--color-warning-surface: #fff7e6;
--color-warning-text: #7a5200;
--color-star: #b7791f;
/* ── 간격 (이름의 숫자 = px) ─────────────── */
--space-1: 0.0625rem;
--space-2: 0.125rem;
--space-3: 0.1875rem;
--space-4: 0.25rem;
--space-6: 0.375rem;
--space-8: 0.5rem;
--space-9: 0.5625rem;
--space-10: 0.625rem;
--space-12: 0.75rem;
--space-14: 0.875rem;
--space-16: 1rem;
--space-18: 1.125rem;
--space-20: 1.25rem;
--space-22: 1.375rem;
--space-24: 1.5rem;
--space-28: 1.75rem;
--space-36: 2.25rem;
--space-40: 2.5rem;
--space-44: 2.75rem;
--space-48: 3rem;
--space-56: 3.5rem;
--space-80: 5rem;
/* ── 모서리 ─────────────────────────────── */
--radius-xs: 0.25rem; /* 받는 사람 토큰 */
--radius-sm: 0.375rem;
--radius-item: 0.4375rem; /* 메일함 항목 */
--radius-md: 0.5rem; /* 버튼 · 입력 */
--radius-lg: 0.625rem; /* 레일 항목 · 알림 */
--radius-xl: 0.75rem; /* 대화상자 */
--radius-round: 50%;
/* ── 선 · 그림자 ────────────────────────── */
--border-width: 0.0625rem;
--focus-ring-width: 0.125rem;
--focus-ring-offset: 0.125rem;
--shadow-toast: 0 0.625rem 1.75rem rgba(16, 24, 28, 0.12);
--shadow-toast-strong: 0 0.625rem 1.75rem rgba(16, 24, 28, 0.22);
--shadow-dialog: 0 0.875rem 2.25rem rgba(16, 24, 28, 0.14);
--color-backdrop: rgba(11, 54, 64, 0.32);
/* ── 크기 ───────────────────────────────── */
--size-touch: 2.75rem; /* 44px — 누를 수 있는 최소 영역 */
/*
* 밀도를 위한 작은 조작 영역 — 클릭 영역 44px 기준의 의도된 예외 (디자인 README "디자인 원칙", 사용자 확정).
* 목록 표의 별 버튼(폭 32px · 행 높이를 채움)과 "분할 | 목록" 전환(바깥 36px)에만 쓴다.
*/
--size-compact-control: 2rem; /* 32px */
--size-segment-item: 1.875rem; /* 30px — 전환 버튼 한 칸 높이 (테두리·안쪽 여백 포함 36px) */
--size-icon-button: 2.5rem; /* 40px — 새로고침 · 이전/다음 버튼 폭 (높이는 44px) */
--size-rail: 4.5rem; /* 72px — 앱 레일 폭 */
--size-rail-item: 3.75rem; /* 60px */
--size-row-min: 2.5rem; /* 40px — 메일함 항목 */
--size-header-min: 3.25rem; /* 52px — 목록 · 본문 머리 */
--size-topbar-min: 3.5rem; /* 56px — 상단 바 */
/* ── 메일 목록 행 ───────────────────────── */
--size-list-row: 3.125rem; /* 50px — 분할 보기 2줄 행 */
--size-table-head: 2rem; /* 32px — 목록 보기 표 머리 줄 */
--size-table-row: 2.375rem; /* 38px — 목록 보기 표 행 */
--table-gap: 0.75rem; /* 12px — 표 칸 사이 */
--table-col-star: 2rem; /* 32px */
--table-col-who: 10.5rem; /* 168px */
--table-col-who-narrow: 7rem; /* 112px — 600px 미만 */
--table-col-date: 5.25rem; /* 84px */
--table-col-date-narrow: 4.5rem; /* 72px — 600px 미만 */
--table-col-size: 4rem; /* 64px */
--table-min-width: 25rem; /* 400px — 이보다 좁으면 가로 스크롤 */
/*
* ── 콘텐츠 열 (읽기 · 쓰기 공통) ─────────────
* 분할 보기 읽기 · 목록 보기 읽기 · 쓰기 세 곳이 이 값만 쓴다 (디자인 README "콘텐츠 열").
*/
--content-max-width: 50rem; /* 800px */
--content-inline: 2rem; /* 32px — 좌우 여백. 제목·입력·툴바 첫 아이콘이 모두 이 선에서 시작한다 */
--content-block-start: 1.25rem; /* 20px */
--content-block-end: 2.25rem; /* 36px */
--content-measure: 42.5rem; /* 680px — 본문 글줄 최대 폭 */
--toolbar-button-inline: 0.625rem; /* 10px — 툴바 버튼 안쪽 좌우 여백 */
/* 22px — 툴바 안쪽 여백. 첫 버튼의 안쪽 여백(10px)을 더하면 아이콘이 32px 선에 온다 */
--content-toolbar-start: calc(var(--content-inline) - var(--toolbar-button-inline));
/* 744px — 쓰기 본문 입력칸 = 글줄 680 + 좌우 여백 */
--compose-body-max-width: calc(var(--content-measure) + var(--content-inline) * 2);
}