/* 앱 컴포넌트: 모바일 앱 화면을 만드는 조각들.
   웹 컴포넌트와 토큰을 공유하되, 손가락과 한 손 조작을 전제로 치수가 다르다. */

/* ══ 앱 껍데기 ═════════════════════════════════════════ */
.app{display:flex;flex-direction:column;min-height:100%;background:var(--canvas);overflow:hidden}
.app__body{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  overscroll-behavior-y:contain;
  padding-bottom:calc(var(--safe-b) + var(--tabbar-h) + 8px)}
.app--no-tabbar .app__body{padding-bottom:calc(var(--safe-b) + 16px)}

/* 앱 헤더: 뒤로가기·제목·동작 하나. 제목은 스크롤하면 작아진다. */
/* 바탕은 ::before 에 깐다(흐림이 요소 자신에 걸리면 안쪽 메뉴·툴팁의 position:fixed 가 바에 갇힌다. components.css .nav 와 같은 이유). */
.appbar{position:sticky;top:0;z-index:var(--z-header);flex:none;
  padding-top:var(--safe-t);isolation:isolate}
.appbar::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;background:var(--canvas);
  transition:background-color var(--d-3) var(--ease-inout),box-shadow var(--d-3) var(--ease-inout)}
.appbar[data-stuck]::before{background:color-mix(in srgb,var(--canvas) 84%,transparent);
  backdrop-filter:saturate(180%) blur(14px);-webkit-backdrop-filter:saturate(180%) blur(14px);
  box-shadow:inset 0 -1px 0 var(--line)}
.appbar__row{display:flex;align-items:center;gap:4px;height:52px;padding-inline:6px}
.appbar__btn{position:relative;display:grid;place-items:center;width:44px;height:44px;border-radius:var(--r-md);
  color:var(--ink);flex:none;
  transition:background-color var(--d-1) var(--ease-inout)}
.appbar__btn:active{background:var(--tint-active)}
@media (hover:hover){.appbar__btn:hover{background:var(--tint)}}
.appbar__btn:focus-visible{border-radius:var(--r-md);outline-offset:-2px}
/* 앱바 버튼의 수 배지(오늘 화면 종 버튼의 확인할 일). 탭바 배지와 같은 색·크기이고, 버튼 44px 안 오른쪽 위에서 왼쪽으로 자란다. 0이면 그리지 않는다. */
.appbar__count{position:absolute;top:var(--s-4);right:var(--s-2);
  min-width:17px;height:17px;padding-inline:5px;border-radius:var(--r-full);
  background:var(--danger);color:var(--danger-ink);font-size:10px;line-height:17px;text-align:center;font-weight:700;
  font-variant-numeric:tabular-nums;pointer-events:none;box-shadow:0 0 0 2px var(--canvas)}
.appbar__title{flex:1;min-width:0;text-align:center;font-size:var(--t-body-md);font-weight:600;
  color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  opacity:0;transition:opacity var(--d-3) var(--ease-inout)}
.appbar[data-stuck] .appbar__title{opacity:1}
/* 큰 제목: 스크롤하면 위 작은 제목으로 넘어간다 */
.appbar__large{padding:4px 20px 14px;font-size:var(--t-heading-lg);line-height:var(--t-heading-lg-lh);
  letter-spacing:var(--t-heading-lg-ls);font-weight:700;color:var(--ink);
  transition:opacity var(--d-3) var(--ease-inout),transform var(--d-3) var(--ease-inout)}
.appbar[data-stuck] .appbar__large{opacity:0;transform:translateY(-6px)}

/* 하단 탭바: 앱 껍데기 안에서는 절대 위치, 실제 화면에서는 .tabbar--fixed 로 고정한다 */
.tabbar{position:absolute;left:0;right:0;bottom:0;z-index:var(--z-sticky);
  display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
  height:calc(var(--tabbar-h) + var(--safe-b));padding-bottom:var(--safe-b);
  /* 불투명 바탕: 뒤로 지나가는 말풍선 글자가 탭 이름 사이로 비치지 않게 한다(입력 바 .dock 과 같게).
     97% 로 두고 흐림을 섞어도 다크 모드에서 글자 윤곽이 남아 보여서 흐림 없이 바탕색만 쓴다. */
  background:var(--canvas);
  box-shadow:inset 0 1px 0 var(--line)}
.tabbar--fixed{position:fixed}
.tabbar__item{position:relative;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:var(--s-4);min-height:var(--c-md-h);padding-block:var(--s-8);color:var(--ink-subtle);
  font-size:var(--t-nano);line-height:var(--t-nano-lh);font-weight:600;-webkit-tap-highlight-color:transparent;
  transition:color var(--d-2) var(--ease-inout)}
/* 지금 탭: 강조색 + 굵은 글자(색만으로 알리지 않는다). 화면 읽기는 aria-current="page". */
.tabbar__item[aria-current="page"]{color:var(--accent-text);font-weight:700}
.tabbar__item:active{opacity:.6}
.tabbar__item:focus-visible{outline-offset:-4px;border-radius:var(--r-md)}
@media (hover:hover){.tabbar__item:hover:not([aria-current="page"]){color:var(--ink-muted)}}
.tabbar__icon{display:block;flex:none;width:var(--s-24);height:var(--s-24)}
.tabbar__dot{position:absolute;top:8px;left:50%;translate:8px 0;
  min-width:8px;height:8px;border-radius:var(--r-full);background:var(--bad);box-shadow:0 0 0 2px var(--canvas)}
.tabbar__count{position:absolute;top:5px;left:50%;translate:6px 0;
  min-width:17px;height:17px;padding-inline:5px;border-radius:var(--r-full);
  background:var(--danger);color:var(--danger-ink);font-size:10px;line-height:17px;text-align:center;font-weight:700;
  font-variant-numeric:tabular-nums;box-shadow:0 0 0 2px var(--canvas)}

/* 하단 고정 동작 바: 화면의 결론. 항상 같은 자리에 둔다. */
/* 안내 한 줄(.actionbar__hint)은 버튼 위 한 줄을 다 쓰고 버튼은 다음 줄에 나란히(flex-wrap). */
.actionbar{position:sticky;bottom:0;z-index:var(--z-sticky);
  display:flex;flex-wrap:wrap;gap:8px;padding:12px 16px calc(12px + var(--safe-b));isolation:isolate}
.actionbar::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:color-mix(in srgb,var(--canvas) 92%,transparent);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  box-shadow:inset 0 1px 0 var(--line)}
.actionbar > *{flex:1;min-width:0}
.actionbar--stack{flex-direction:column}
.actionbar__hint{flex-basis:100%;text-align:center;font-size:var(--t-caption);color:var(--ink-muted);
  margin-bottom:2px}

/* 떠 있는 동작 버튼 */
.fab{position:fixed;right:16px;bottom:calc(var(--safe-b) + var(--tabbar-h) + 16px);
  z-index:var(--z-sticky);display:inline-flex;align-items:center;gap:8px;
  height:52px;padding-inline:18px;border-radius:var(--r-full);
  background:var(--accent);color:var(--accent-ink);font-size:var(--t-body-sm);font-weight:600;
  box-shadow:var(--sh-lg);
  transition:transform var(--d-2) var(--ease-spring),box-shadow var(--d-2) var(--ease-inout)}
.fab:active{transform:scale(.95)}
@media (hover:hover){.fab:hover{box-shadow:var(--sh-xl)}}
.fab:focus-visible{border-radius:var(--r-full)}
.fab--round{padding:0;width:52px;justify-content:center}

/* ══ 리스트 ═══════════════════════════════════════════
   앱 화면의 대부분은 목록이다. 여기가 제일 중요하다. */
.list{display:flex;flex-direction:column}
.list__header{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  padding:20px 20px 8px;font-size:var(--t-body-sm);font-weight:600;color:var(--ink-muted)}
.list__header .link{font-weight:500;font-size:var(--t-caption)}

/* 행: 앞(아이콘·아바타) · 가운데(제목·부제) · 뒤(값·화살표) */
.row-item{display:flex;align-items:center;gap:14px;width:100%;
  min-height:64px;padding:12px 20px;text-align:left;
  -webkit-tap-highlight-color:transparent;
  transition:background-color var(--d-1) var(--ease-inout)}
/* 누를 수 있는 행(a·button·label)만 눌림·호버를 준다 */
:is(a,button,label).row-item{cursor:pointer;color:inherit}
:is(a,button,label).row-item:active{background:var(--tint)}
@media (hover:hover){:is(a,button,label).row-item:hover{background:var(--tint)}}
.row-item:focus-visible{outline-offset:-2px;border-radius:var(--r-sm)}
.row-item__lead{flex:none;display:grid;place-items:center;width:44px;height:44px;
  border-radius:var(--r-md);background:var(--canvas-alt);color:var(--ink-muted);overflow:hidden}
.row-item__lead--round{border-radius:var(--r-full)}
.row-item__lead--accent{background:var(--accent-soft);color:var(--accent-text)}
.row-item__main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
.row-item__title{font-size:var(--t-body-sm);font-weight:600;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row-item__sub{font-size:var(--t-caption);color:var(--ink-muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row-item__tail{flex:none;display:flex;align-items:center;gap:8px;
  font-size:var(--t-body-sm);color:var(--ink-muted);font-variant-numeric:tabular-nums}
.row-item__value{font-weight:600;color:var(--ink)}
.row-item__chev{color:var(--ink-disabled);flex:none}
.row-item--tall{min-height:76px}
/* 긴 제목·설명을 말줄임 대신 줄바꿈한다(문장이 제목인 목록). 앞·뒤 칸은 첫 줄에 맞춘다. */
.row-item--wrap{align-items:flex-start}
.row-item--wrap .row-item__title,.row-item--wrap .row-item__sub{white-space:normal;overflow:visible;word-break:keep-all;overflow-wrap:anywhere}
.row-item--wrap .row-item__tail{min-height:24px}
/* 둘째 줄을 두 줄까지 보이고 말줄임 */
.row-item .row-item__sub--2,.row-item--wrap .row-item__sub--2{white-space:normal;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
/* 행 사이 구분선은 앞 요소 폭만큼 들여쓴다 */
.list--divided .row-item{box-shadow:inset 0 -1px 0 var(--line)}
.list--divided .row-item:last-child{box-shadow:none}
.list--inset-divider .row-item{position:relative}
.list--inset-divider .row-item::after{content:"";position:absolute;left:78px;right:0;bottom:0;
  height:1px;background:var(--line)}
.list--inset-divider .row-item:last-child::after{display:none}

/* 묶음 목록: 카드 안에 행을 모은다 */
.group{position:relative;margin:12px 16px;background:var(--surface);border-radius:var(--r-xl);
  overflow:hidden;isolation:isolate}
.group::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  box-shadow:inset 0 0 0 1px var(--line);z-index:3}
.group .row-item{padding-inline:16px}
.group__label{padding:20px 20px 8px;font-size:var(--t-caption);font-weight:600;color:var(--ink-muted)}

/* 스와이프 액션: 행을 왼쪽으로 밀면 뒤에서 버튼이 나온다 */
.swipe{position:relative;overflow:hidden;background:var(--canvas)}
.swipe__actions{position:absolute;inset:0 0 0 auto;display:flex}
.swipe__action{display:grid;place-items:center;align-content:center;width:80px;
  color:var(--danger-ink);font-size:var(--t-caption);font-weight:600;gap:4px}
.swipe__action--del{background:var(--danger);color:var(--danger-ink)}
.swipe__action--keep{background:var(--accent);color:var(--accent-ink)}
.swipe__action:focus-visible{outline-color:var(--danger-ink);outline-offset:-4px}
.swipe__front{position:relative;background:var(--canvas);
  transition:transform var(--d-3) var(--ease-out);touch-action:pan-y}
.swipe[data-open] .swipe__front{transform:translateX(var(--swipe-w,-80px))}

/* ══ 입력: 앱 전용 ═════════════════════════════════════ */
/* 금액 입력: 큰 숫자 한 줄. 키패드가 화면 절반을 먹는 걸 전제로 한다. */
.amount{display:flex;flex-direction:column;gap:6px;padding:24px 20px}
.amount__label{font-size:var(--t-body-sm);color:var(--ink-muted)}
.amount__field{display:flex;align-items:baseline;gap:4px;min-width:0}
.amount__input{flex:0 1 auto;min-width:1ch;max-width:100%;field-sizing:content;
  border:0;background:transparent;padding:0;
  font-size:var(--t-display-sm);line-height:1.2;letter-spacing:var(--t-display-sm-ls);
  font-weight:700;color:var(--ink);font-variant-numeric:tabular-nums;outline:none;caret-color:var(--accent)}
.amount__input::placeholder{color:var(--ink-disabled)}
/* 한도 초과 같은 오류: 숫자와 안내 줄이 함께 빨개지고 한 번 흔들린다 */
.amount__input[aria-invalid="true"]{color:var(--bad-text);animation:shake var(--d-5) var(--ease-out)}
.amount:has(.amount__input[aria-invalid="true"]) .amount__hint{color:var(--bad-text);font-weight:500}
.amount__unit{font-size:var(--t-heading-md);font-weight:700;color:var(--ink);flex:none}
.amount__hint{font-size:var(--t-caption);color:var(--ink-muted)}
.amount__quick{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}

/* 인증번호: 칸이 나뉜 것처럼 보이지만 입력은 하나다 */
.otp{position:relative;display:flex;gap:8px;justify-content:center}
.otp__input{position:absolute;inset:0;opacity:0;width:100%;height:100%;
  font-size:16px;letter-spacing:2em;border:0;background:transparent}
.otp__cell{width:46px;height:56px;border-radius:var(--r-md);background:var(--canvas-alt);
  display:grid;place-items:center;font-size:var(--t-heading-md);font-weight:700;color:var(--ink);
  box-shadow:inset 0 0 0 1px var(--line);font-variant-numeric:tabular-nums;
  transition:box-shadow var(--d-2) var(--ease-inout),background-color var(--d-2) var(--ease-inout)}
.otp__cell{position:relative}
.otp__cell[data-on]{background:var(--surface);box-shadow:inset 0 0 0 2px var(--accent)}
.otp__cell[data-filled]{background:var(--surface);box-shadow:inset 0 0 0 1px var(--line-strong)}
.otp__cell[data-filled][data-on]{box-shadow:inset 0 0 0 2px var(--accent)}
/* 지금 칸이 비어 있으면 깜빡이는 커서 */
.otp__cell[data-on]:empty::after{content:"";width:2px;height:24px;border-radius:1px;background:var(--accent);animation:caret-blink 1.06s steps(1) infinite}
@keyframes caret-blink{50%{opacity:0}}
/* 틀린 번호: 칸 테두리가 빨개지고 한 번 흔들린다(입력에 aria-invalid="true", 안내 글자는 아래 .field__error) */
.otp:has(.otp__input[aria-invalid="true"]){animation:shake var(--d-5) var(--ease-out)}
.otp:has(.otp__input[aria-invalid="true"]) .otp__cell{background:var(--bad-soft);box-shadow:inset 0 0 0 1px var(--bad)}
.otp:has(.otp__input:disabled) .otp__cell{color:var(--ink-disabled);background:var(--canvas-alt)}

/* 수량 조절 */
.stepper-num{display:inline-flex;align-items:center;gap:2px;background:var(--canvas-alt);
  border-radius:var(--r-md);padding:3px}
.stepper-num__btn{position:relative;display:grid;place-items:center;width:38px;height:38px;border-radius:var(--r-sm);
  color:var(--ink);-webkit-tap-highlight-color:transparent;touch-action:manipulation;
  transition:background-color var(--d-1) var(--ease-inout),color var(--d-1) var(--ease-inout)}
/* 보이는 칸은 38px, 누르는 범위는 바깥 틀(3px)까지 44px. 가운데 숫자는 누르는 곳이 아니라 겹쳐도 된다. */
.stepper-num__btn::before{content:"";position:absolute;inset:-3px}
.stepper-num__btn:active:not(:disabled){background:var(--tint-active)}
@media (hover:hover){.stepper-num__btn:hover:not(:disabled){background:var(--tint)}}
.stepper-num__btn:focus-visible{outline-offset:0;border-radius:var(--r-sm)}
.stepper-num__btn:disabled{color:var(--ink-disabled);cursor:not-allowed}
.stepper-num__val{min-width:40px;text-align:center;font-size:var(--t-body-sm);font-weight:700;
  color:var(--ink);font-variant-numeric:tabular-nums}

/* 검색 바 */
/* 검색 바: 알약 입력. 포커스하면 입력칸과 같은 강조 테두리·링. 글자가 있으면 지우기 버튼(app.js)이 뜬다. */
.searchbar{position:relative;display:flex;align-items:center;gap:10px;height:44px;padding-inline:14px 4px;
  margin:8px 16px;background:var(--canvas-alt);border-radius:var(--r-full);color:var(--ink-subtle);
  box-shadow:inset 0 0 0 1px var(--line);
  transition:background-color var(--d-2) var(--ease-inout),box-shadow var(--d-2) var(--ease-inout)}
.searchbar > svg,.searchbar > .ico{flex:none}
@media (hover:hover){.searchbar:hover:not(:focus-within){box-shadow:inset 0 0 0 1px var(--line-strong)}}
.searchbar:focus-within{background:var(--surface);color:var(--ink-muted);box-shadow:inset 0 0 0 1px var(--accent),0 0 0 3px var(--accent-soft)}
.searchbar__input{flex:1;min-width:0;height:100%;border:0;background:transparent;outline:none;appearance:none;
  font-size:var(--t-body-md);color:var(--ink);caret-color:var(--accent);padding-right:10px}
.searchbar__input::placeholder{color:var(--ink-disabled)}
.searchbar__input::-webkit-search-decoration,.searchbar__input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
.searchbar__input:focus-visible{outline:none}
/* 지우기: 보이는 원 22px, 누르는 범위 40×44 */
.searchbar__clear{position:relative;flex:none;display:grid;place-items:center;width:40px;height:44px;margin-block:-2px;color:var(--ink-subtle);cursor:pointer;
  -webkit-tap-highlight-color:transparent}
.searchbar__clear::after{content:"";position:absolute;inset:0;margin:auto;width:22px;height:22px;border-radius:50%;background:var(--ink-disabled);
  transition:background-color var(--d-1) var(--ease-inout)}
.searchbar__clear svg{position:relative;z-index:1;width:12px;height:12px;color:var(--surface)}
@media (hover:hover){.searchbar__clear:hover::after{background:var(--ink-subtle)}}
.searchbar__clear:active::after{background:var(--ink-muted)}
.searchbar__clear:focus-visible{outline-offset:-6px;border-radius:var(--r-full)}
.searchbar__clear[hidden]{display:none}

/* 필터 칩 줄 */
/* 스냅 자리를 여백 안쪽에 둔다(scroll-padding). 없으면 첫 그림에서 첫 칩이 여백만큼 밀려 가장자리에 붙는다. */
.chiprow{display:flex;gap:8px;overflow-x:auto;scrollbar-width:none;overscroll-behavior-x:contain;
  padding:10px 16px;scroll-padding-inline:16px;scroll-snap-type:x proximity}
.chiprow::-webkit-scrollbar{display:none}
/* 필터 칩: 보이는 높이 36px, 누르는 범위는 위아래로 넓혀 44px(옆 칩과는 떨어져 있어 가로로는 넓히지 않는다).
   고른 칩은 반전 면(진한 면·밝은 글자)이라 색만이 아니라 명도로 구분된다. 화면 읽기는 aria-pressed. */
.chip-f{position:relative;flex:none;display:inline-flex;align-items:center;gap:5px;height:36px;padding-inline:14px;
  border-radius:var(--r-full);background:var(--canvas-alt);color:var(--ink-body);
  font-size:var(--t-body-xs);font-weight:600;white-space:nowrap;scroll-snap-align:start;cursor:pointer;
  box-shadow:inset 0 0 0 1px var(--line);-webkit-tap-highlight-color:transparent;
  transition:background-color var(--d-2) var(--ease-inout),color var(--d-2) var(--ease-inout),box-shadow var(--d-2) var(--ease-inout),transform var(--d-1) var(--ease-out)}
.chip-f::before{content:"";position:absolute;inset:-4px 0}
.chip-f:active:not(:disabled){transform:scale(.96)}
/* 호버는 틴트를 한 겹 얹는다(라이트는 살짝 진하게, 다크는 살짝 밝게). sunken 면은 다크에서 더 어두워져 눌린 것처럼 읽힌다. */
@media (hover:hover){.chip-f:hover:not([aria-pressed="true"],:disabled){background:linear-gradient(var(--tint),var(--tint)),var(--canvas-alt);box-shadow:inset 0 0 0 1px var(--line-strong)}}
.chip-f:focus-visible{border-radius:var(--r-full);outline-offset:1px}
.chip-f[aria-pressed="true"]{background:var(--ink);color:var(--canvas);box-shadow:none}
.chip-f:disabled,.chip-f[aria-disabled="true"]{color:var(--ink-disabled);cursor:not-allowed}
/* 개수: 작은 알약. 고른 칩 위에서는 반전 면에 맞춰 옅게 */
.chip-f__count{min-width:18px;height:18px;padding-inline:5px;border-radius:var(--r-full);background:var(--tint-hover);
  font-size:var(--t-nano);line-height:18px;font-weight:700;text-align:center;font-variant-numeric:tabular-nums;color:inherit}
.chip-f[aria-pressed="true"] .chip-f__count{background:color-mix(in srgb,var(--canvas) 22%,transparent)}
/* 아래 화살표: 누르면 선택 목록이 열리는 칩(기간·정렬). 값이 정해지면 aria-pressed="true" */
.chip-f__caret{flex:none;width:14px;height:14px;margin-right:-4px;opacity:.7;transition:transform var(--d-3) var(--ease-out)}
.chip-f[aria-expanded="true"] .chip-f__caret{transform:rotate(180deg)}
/* 초기화: 칩 줄 맨 앞(또는 끝)의 테두리만 있는 칩. 고른 칩이 없으면 숨긴다(app.js) */
.chip-f--reset{background:transparent;color:var(--ink-muted);box-shadow:inset 0 0 0 1px var(--line-strong)}
.chip-f--reset[hidden]{display:none}

/* ══ 알림 배너 ════════════════════════════════════════ */
.callout{display:flex;gap:12px;padding:14px 16px;border-radius:var(--r-lg);
  font-size:var(--t-body-sm);line-height:1.55}
/* 아이콘은 첫 줄 높이(1.55em) 가운데에 둔다. 16·18·20px 어느 크기든 첫 줄과 맞는다. */
.callout__icon{flex:none;display:grid;place-items:center;height:1.55em}
.callout__body{flex:1;min-width:0}
.callout__title{display:block;font-weight:600;margin-bottom:2px}
.callout--info{background:var(--accent-soft);color:var(--accent-text)}
.callout--warn{background:var(--warn-soft);color:var(--warn-text)}
.callout--bad{background:var(--bad-soft);color:var(--bad-text)}
.callout--good{background:var(--good-soft);color:var(--good-text)}
.callout--plain{background:var(--canvas-alt);color:var(--ink-body)}

/* ══ 진행 표시 ════════════════════════════════════════ */
.steps{display:flex;align-items:center;gap:6px;padding:0 20px}
.steps__bar{flex:1;height:3px;border-radius:var(--r-full);background:var(--line);overflow:hidden}
.steps__bar > span{display:block;height:100%;width:0;background:var(--accent);border-radius:inherit;
  transition:width var(--d-5) var(--ease-out)}
/* 끝을 모르는 진행(업로드 준비 등): 짧은 막대가 왼쪽에서 오른쪽으로 흐른다. 모션 축소면 가운데 멈춘 막대. */
.steps__bar[data-indeterminate] > span{width:36%;animation:bar-flow 1.3s var(--ease-inout) infinite}
@keyframes bar-flow{from{transform:translateX(-100%)}to{transform:translateX(280%)}}
@media (prefers-reduced-motion:reduce){.steps__bar[data-indeterminate] > span{animation:none;transform:translateX(90%)}}
.steps__bar[data-state="bad"] > span{background:var(--bad)}
.steps__bar[data-state="good"] > span{background:var(--good)}
.steps__label{flex:none;font-size:var(--t-micro);color:var(--ink-muted);font-variant-numeric:tabular-nums}

/* 고리의 가운데는 칠하지 않고 뚫는다(mask). 카드·다크 면 어디에 얹어도 가운데가 바탕색과 어긋나지 않는다. */
.ring{--p:0;--size:52px;--ring-w:4px;width:var(--size);height:var(--size);border-radius:50%;
  background:conic-gradient(var(--accent) calc(var(--p)*1%),var(--line) 0);
  -webkit-mask:radial-gradient(farthest-side,transparent calc(100% - var(--ring-w)),#000 calc(100% - var(--ring-w) + .5px));
          mask:radial-gradient(farthest-side,transparent calc(100% - var(--ring-w)),#000 calc(100% - var(--ring-w) + .5px));
  display:grid;place-items:center;flex:none}
.ring::after{content:"";width:calc(var(--size) - 8px);height:calc(var(--size) - 8px);
  border-radius:50%;background:var(--canvas)}
.ring__num{position:absolute;font-size:var(--t-micro);font-weight:700;color:var(--ink);
  font-variant-numeric:tabular-nums}
.ring-wrap{position:relative;display:grid;place-items:center}

/* ══ 아바타 ═══════════════════════════════════════════ */
/* 사진이 없으면 글자 한 자를 넣어도 원 가운데에 와요(inline-grid). 사진(img)은 그대로 덮어요 */
.avatar{display:inline-grid;place-items:center;width:40px;height:40px;border-radius:var(--r-full);object-fit:cover;flex:none;
  background:var(--canvas-alt);line-height:1}
.avatar--sm{width:28px;height:28px}
.avatar--lg{width:56px;height:56px}
.avatar-stack{display:flex}
.avatar-stack > *{margin-left:-10px;box-shadow:0 0 0 2px var(--canvas)}
.avatar-stack > *:first-child{margin-left:0}
.avatar-more{display:grid;place-items:center;background:var(--canvas-alt);color:var(--ink-muted);
  font-size:var(--t-nano);font-weight:700}

/* ══ 당겨서 새로고침 표시 ══════════════════════════════ */
.pull{display:grid;place-items:center;height:0;overflow:hidden;color:var(--ink-subtle);
  transition:height var(--d-3) var(--ease-out)}
.pull[data-on]{height:48px}

/* ══ 목록 뼈대 ════════════════════════════════════════ */
.skel-row{display:flex;align-items:center;gap:14px;padding:14px 20px}
.skel-row__lead{width:44px;height:44px;border-radius:var(--r-md);flex:none}
.skel-row__main{flex:1;display:flex;flex-direction:column;gap:8px}

/* ══ 달력 ═════════════════════════════════════════════
   월 단위 격자. 하루 칸은 44px 를 넘지 않으면 손가락으로 못 누른다. */
.cal{width:100%;max-width:340px}
.cal__head{display:flex;align-items:center;justify-content:space-between;gap:8px;
  padding:4px 4px 14px}
.cal__title{font-size:var(--t-body-md);font-weight:700;color:var(--ink);
  font-variant-numeric:tabular-nums}
.cal__nav{position:relative;display:grid;place-items:center;width:36px;height:36px;border-radius:var(--r-sm);
  color:var(--ink-muted);transition:background-color var(--d-1) var(--ease-inout)}
.cal__nav::before{content:"";position:absolute;inset:-4px}
@media (hover:hover){.cal__nav:hover{background:var(--tint);color:var(--ink)}}
.cal__nav:active{background:var(--tint-active)}
.cal__nav:disabled{color:var(--ink-disabled);pointer-events:none}
.cal__dows,.cal__grid{display:grid;grid-template-columns:repeat(7,1fr)}
.cal__dow{text-align:center;padding-block:6px;font-size:var(--t-nano);font-weight:600;
  color:var(--ink-subtle)}
.cal__dow:first-child{color:var(--bad-text)}
.cal__dow:last-child{color:var(--accent-text)}
/* 좁은 카드 안에서 칸 폭이 44px 보다 작아져도 누르는 높이는 44px 를 지킨다 */
.cal__cell{position:relative;aspect-ratio:1;min-height:44px;display:grid;place-items:center;
  font-size:var(--t-body-sm);font-weight:500;color:var(--ink-body);
  border-radius:var(--r-md);cursor:pointer;
  transition:background-color var(--d-1) var(--ease-inout),color var(--d-1) var(--ease-inout)}
@media (hover:hover){.cal__cell:hover:not(:disabled,[aria-pressed="true"]){background:var(--tint)}}
.cal__cell:active:not(:disabled,[aria-pressed="true"]){background:var(--tint-active)}
.cal__cell:focus-visible{outline-offset:-2px;border-radius:var(--r-md);z-index:1}
.cal__cell:disabled{color:var(--ink-disabled);cursor:not-allowed}
.cal__cell[data-out]{color:var(--ink-subtle)}
/* 오늘(data-today)은 점이 아니라 굵은 강조색 숫자. 점은 '일정 있음' 하나만 뜻하게 해서, 오늘에 일정이 있어도 둘이 겹치지 않는다. */
.cal__cell[data-today]{color:var(--accent-text);font-weight:700}
.cal__cell[aria-pressed="true"]{background:var(--accent);color:var(--accent-ink);font-weight:700}
/* 일정이 있는 날(data-mark): 숫자 아래 점. 다른 달 칸은 흐리게, 고른 날은 글자색 점. 화면 읽기는 '일정 있음'을 읽는다(app.js). */
.cal__cell[data-mark]::after{content:"";position:absolute;bottom:6px;width:5px;height:5px;
  border-radius:50%;background:var(--ink-muted)}
.cal__cell[data-out][data-mark]::after{background:var(--ink-disabled)}
.cal__cell[aria-pressed="true"][data-mark]::after{background:var(--accent-ink)}
.cal__cell[data-in-range]{background:var(--accent-soft);border-radius:0}
.cal__cell[data-range-start]{border-radius:var(--r-md) 0 0 var(--r-md)}
.cal__cell[data-range-end]{border-radius:0 var(--r-md) var(--r-md) 0}

/* ══ 키패드 ═══════════════════════════════════════════
   금액·인증번호 입력에 쓴다. 화면 절반을 차지하는 걸 전제로 한다. */
.keypad{display:grid;grid-template-columns:repeat(3,1fr);gap:2px;
  padding:8px 8px calc(8px + var(--safe-b));background:var(--canvas-alt)}
.keypad__key{display:grid;place-items:center;height:56px;border-radius:var(--r-md);
  font-size:var(--t-heading-md);font-weight:600;color:var(--ink);
  font-variant-numeric:tabular-nums;
  transition:background-color 80ms var(--ease-inout)}
.keypad__key:active{background:var(--tint-active)}
.keypad__key--fn{font-size:var(--t-body-md);color:var(--ink-muted)}

/* ══ 온보딩 ═══════════════════════════════════════════ */
.onb{display:flex;flex-direction:column;height:100%}
.onb__track{flex:1;display:flex;overflow-x:auto;scroll-snap-type:x mandatory;
  scrollbar-width:none;overscroll-behavior-x:contain}
.onb__track::-webkit-scrollbar{display:none}
.onb__slide{flex:0 0 100%;scroll-snap-align:center;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:16px;padding:32px 24px;text-align:center}
.onb__art{width:100%;max-width:240px;aspect-ratio:1;border-radius:var(--r-3xl);
  background:var(--canvas-alt);display:grid;place-items:center;margin-bottom:8px}
.onb__dots{display:flex;justify-content:center;padding:4px 16px 12px}
.onb__dot{display:grid;place-items:center;width:32px;height:44px;cursor:pointer}
.onb__dot::after{content:"";width:6px;height:6px;border-radius:3px;background:var(--line-strong);
  transition:width var(--d-3) var(--ease-out),background-color var(--d-2) var(--ease-inout)}
.onb__dot[aria-current="true"]::after{width:20px;background:var(--accent)}

/* ══ 첨부 ═════════════════════════════════════════════ */
.drop{display:flex;flex-direction:column;align-items:center;gap:8px;
  padding:32px 20px;border-radius:var(--r-xl);text-align:center;cursor:pointer;
  background:var(--canvas-alt);box-shadow:inset 0 0 0 1.5px var(--line-strong);
  transition:background-color var(--d-2) var(--ease-inout),box-shadow var(--d-2) var(--ease-inout)}
@media (hover:hover){.drop:hover{background:var(--accent-soft);box-shadow:inset 0 0 0 1.5px var(--accent)}}
.drop[data-over]{background:var(--accent-soft);box-shadow:inset 0 0 0 1.5px var(--accent)}
.drop:focus-visible{border-radius:var(--r-xl)}
.drop__icon{color:var(--ink-subtle)}
.drop[data-over] .drop__icon{color:var(--accent-text)}
.filechip{display:inline-flex;align-items:center;gap:8px;height:36px;padding:0 6px 0 12px;
  border-radius:var(--r-full);background:var(--canvas-alt);
  font-size:var(--t-body-xs);color:var(--ink-body);max-width:100%}
.filechip__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.filechip__x{position:relative;display:grid;place-items:center;width:24px;height:24px;border-radius:50%;
  color:var(--ink-subtle);flex:none}
.filechip__x::before{content:"";position:absolute;inset:-10px}
@media (hover:hover){.filechip__x:hover{background:var(--tint);color:var(--ink)}}
.filechip__x:active{background:var(--tint-active)}

/* ══ 당겨서 새로고침 ══════════════════════════════════ */
.ptr{position:relative;overflow:hidden}
.ptr__ind{position:absolute;top:0;left:0;right:0;display:grid;place-items:center;
  height:0;color:var(--ink-subtle);overflow:hidden;pointer-events:none}
.ptr__spin{transition:transform var(--d-2) var(--ease-out)}
.ptr[data-ready] .ptr__spin{transform:rotate(180deg);color:var(--accent-text)}
.ptr[data-loading] .ptr__spin{animation:spin .7s linear infinite}
.ptr__body{will-change:transform}

/* ══ 차트 ═════════════════════════════════════════════
   라이브러리를 쓰지 않는다. 막대·꺾은선·도넛이면 대부분 충분하다. */
.chart{width:100%;display:block;overflow:visible}
.chart text{fill:var(--ink-muted);font-size:11px;font-family:var(--font);
  font-variant-numeric:tabular-nums}
.chart .grid-line{stroke:var(--line);stroke-width:1}
.chart--bare text,.chart--bare .grid-line{display:none}
.chart .bar{fill:var(--accent);rx:3}
.chart .bar--alt{fill:var(--accent-line)}
.chart .line{fill:none;stroke:var(--accent);stroke-width:2.2;
  stroke-linecap:round;stroke-linejoin:round}
.chart .area{fill:var(--accent-soft)}
.chart .dot{fill:var(--accent);stroke:var(--surface);stroke-width:2.5}
/* 꺾은선 점(app.js 가 circle.dotp 로 그린다): 속이 빈 점, 마지막(강조) 점은 채운 점 */
.chart .dotp{fill:var(--surface);stroke:var(--accent);stroke-width:2}
.chart .dotp--last{fill:var(--accent);stroke:var(--surface);stroke-width:2.5}
/* 도넛 가운데 숫자 */
.chart .chart__center{font-size:26px;font-weight:700;letter-spacing:-.5px;fill:var(--ink)}
.chart .track{fill:none;stroke:var(--line);stroke-width:10;stroke-linecap:round}
.chart .arc{fill:none;stroke:var(--accent);stroke-width:10;stroke-linecap:round;
  transition:stroke-dashoffset var(--d-6) var(--ease-out)}
.chart-legend{display:flex;flex-wrap:wrap;gap:14px;margin-top:12px}
.chart-legend__i{display:inline-flex;align-items:center;gap:6px;
  font-size:var(--t-micro);color:var(--ink-muted)}
.chart-legend__sw{width:10px;height:10px;border-radius:3px;background:var(--accent)}

/* ══ 선택 칩 묶음 ═════════════════════════════════════
   ds.js initChoices 가 aria-pressed 를 관리한다(하나만 고르기 · data-multi 면 여러 개).
   고른 칩은 연한 강조 면 + 강조 테두리 + 강조 글자가 함께 바뀌고, 여러 개 고르기에서는 체크 표시도 붙는다(색만으로 구분하지 않는다).
   (A 가 extras.css 에 먼저 만든 모양을 코어로 옮겼다. 틴트 면 위에서는 흰 면으로 띄운다.) */
.choices{display:flex;flex-wrap:wrap;gap:var(--s-8)}
.choices__item{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:var(--s-6);
  min-height:var(--c-md-h);min-width:var(--c-md-h);padding-inline:var(--s-16);border-radius:var(--r-full);
  background:var(--canvas-alt);color:var(--ink-body);font-size:var(--t-body-sm);font-weight:600;white-space:nowrap;cursor:pointer;
  box-shadow:inset 0 0 0 1px var(--line);-webkit-tap-highlight-color:transparent;
  transition:background-color var(--d-2) var(--ease-inout),color var(--d-2) var(--ease-inout),
             box-shadow var(--d-2) var(--ease-inout),transform var(--d-2) var(--ease-spring)}
.on-alt .choices__item,.surface-alt .choices__item{background:var(--surface)}
@media (hover:hover){.choices__item:hover:not(:disabled,[aria-disabled="true"],[aria-pressed="true"]){background:linear-gradient(var(--tint),var(--tint)),var(--canvas-alt);box-shadow:inset 0 0 0 1px var(--line-strong)}
  .on-alt .choices__item:hover:not(:disabled,[aria-disabled="true"],[aria-pressed="true"]),.surface-alt .choices__item:hover:not(:disabled,[aria-disabled="true"],[aria-pressed="true"]){background:linear-gradient(var(--tint),var(--tint)),var(--surface)}}
.choices__item:active:not(:disabled,[aria-disabled="true"]){transform:scale(.96)}
.choices__item:focus-visible{border-radius:var(--r-full)}
.choices__item[aria-pressed="true"]{background:var(--accent-soft);color:var(--accent-text);box-shadow:inset 0 0 0 1.5px var(--accent)}
.choices[data-multi] .choices__item[aria-pressed="true"]::before{content:"";width:9px;height:5px;margin:-3px 0 0 -2px;
  border:solid currentColor;border-width:0 0 2px 2px;rotate:-45deg;flex:none}
.choices__item:disabled,.choices__item[aria-disabled="true"]{color:var(--ink-disabled);background:var(--canvas-alt);box-shadow:none;cursor:not-allowed;transform:none}
.choices__count{font-size:var(--t-micro);font-weight:500;font-variant-numeric:tabular-nums}
.choices--wrap .choices__item{white-space:normal;text-align:left;justify-content:flex-start;padding-block:var(--s-8);
  line-height:var(--t-body-sm-lh);word-break:keep-all}
.choices--stack{flex-direction:column;align-items:stretch}
.choices--stack .choices__item{justify-content:flex-start;border-radius:var(--r-lg);min-height:var(--c-lg-h)}
.choices--stack .choices__item:focus-visible{border-radius:var(--r-lg)}
/* 2열 칸(좁은 화면에서도 2×2 로, 한 칩만 다음 줄로 떨어지지 않게) */
.choices--grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr))}
.choices--grid .choices__item{width:100%;min-width:0;padding-inline:var(--s-12)}

/* ══ 고정 입력 바 ═════════════════════════════════════
   [앞 버튼][여러 줄 입력칸][마이크 ↔ 보내기]. 입력칸이 폭 대부분을 쓰고, 오른쪽 한 자리는
   비었을 때 마이크(또는 다른 보조 버튼), 글자가 있으면 보내기로 바뀐다(app.js 가 data-has-text).
   탭바 위에 붙는다(--tabbar-h). 탭바가 없는 화면은 .app--no-tabbar. 폰 목업·카드 안에서는 .dock--static. */
.dock{position:fixed;left:0;right:0;bottom:calc(var(--tabbar-h) + var(--safe-b));z-index:var(--z-sticky);
  display:flex;align-items:flex-end;gap:var(--s-8);min-height:var(--dock-h);padding:var(--s-10) var(--s-12);margin-inline:auto;
  max-width:var(--app-w, calc(var(--w-narrow) + var(--s-80)));
  /* 불투명 바탕: 뒤로 스크롤된 말풍선 글자가 입력 바에 비쳐 겹쳐 읽히지 않게 한다 */
  background:var(--canvas);box-shadow:inset 0 1px 0 var(--line);
  transition:bottom var(--d-2) var(--ease-out)}
.app--no-tabbar .dock{bottom:0;padding-bottom:calc(var(--s-10) + var(--safe-b))}
.dock--static{position:relative;left:auto;right:auto;bottom:auto;max-width:none}
.dock__form{display:contents}
.dock__btn,.dock__plus{flex:none;display:grid;place-items:center;width:var(--c-md-h);height:var(--c-md-h);border-radius:var(--r-full);
  color:var(--ink-body);background:var(--canvas-alt);cursor:pointer;-webkit-tap-highlight-color:transparent;
  transition:background-color var(--d-1) var(--ease-inout),transform var(--d-2) var(--ease-spring)}
.dock__plus .ico,.dock__plus svg{transition:rotate var(--d-3) var(--ease-spring)}
.dock__btn:active,.dock__plus:active{background:var(--tint-active);transform:scale(.9)}
@media (hover:hover){.dock__btn:hover,.dock__plus:hover{background:linear-gradient(var(--tint),var(--tint)),var(--canvas-alt)}}
.dock__plus[aria-expanded="true"] .ico,.dock__plus[aria-expanded="true"] svg{rotate:45deg}
.dock__field{flex:1;min-width:0;display:flex;align-items:center;min-height:var(--c-md-h);padding:var(--s-8) var(--s-16);
  border-radius:var(--r-2xl);background:var(--canvas-alt);box-shadow:inset 0 0 0 1.5px transparent;
  transition:box-shadow var(--d-2) var(--ease-inout),background-color var(--d-2) var(--ease-inout)}
.dock__field:focus-within{background:var(--surface);box-shadow:inset 0 0 0 1.5px var(--accent)}
.dock__input{flex:1;display:block;min-width:0;width:100%;margin:0;padding:0;border:0;outline:none;resize:none;background:transparent;
  font-family:inherit;font-size:var(--t-body-md);line-height:var(--t-body-md-lh);color:var(--ink);caret-color:var(--accent);
  field-sizing:content;min-height:var(--t-body-md-lh);max-height:calc(var(--t-body-md-lh) * 5);overflow-y:auto;scrollbar-width:thin;
  word-break:keep-all;overflow-wrap:anywhere}
.dock__input::placeholder{color:var(--ink-subtle)}
.dock__input:focus-visible{outline:none}
.dock__act{position:relative;flex:none;width:var(--c-md-h);height:var(--c-md-h)}
.dock__mic,.dock__send{position:absolute;inset:0;display:grid;place-items:center;border-radius:var(--r-full);cursor:pointer;-webkit-tap-highlight-color:transparent;
  transition:opacity var(--d-2) var(--ease-out),transform var(--d-3) var(--ease-spring),background-color var(--d-1) var(--ease-inout),visibility 0s linear 0s}
.dock__mic{color:var(--ink-body);background:var(--canvas-alt);touch-action:none;user-select:none;-webkit-user-select:none}
.dock__mic:active{background:var(--tint-active)}
.dock__mic[data-rec]{background:var(--bad-soft);color:var(--bad-text);transform:scale(1.1);animation:dock-pulse calc(var(--d-7) + var(--d-2)) var(--ease-inout) infinite}
@keyframes dock-pulse{0%,100%{box-shadow:0 0 0 0 var(--bad-soft)}50%{box-shadow:0 0 0 var(--s-10) transparent}}
.dock__send{color:var(--accent-ink);background:var(--accent);opacity:0;transform:scale(.4) rotate(-90deg);visibility:hidden;pointer-events:none;
  transition:opacity var(--d-1) var(--ease-in),transform var(--d-2) var(--ease-in),visibility 0s linear var(--d-2)}
.dock__send:active{transform:scale(.9)}
.dock__send:disabled{cursor:default}
/* 마이크 자리가 없는 입력 바(보내기만): 보내기를 늘 보이고, 글자가 없으면 흐리게 */
.dock__act:not(:has(.dock__mic)) .dock__send{opacity:.42;transform:none;visibility:visible;pointer-events:auto}
.dock[data-has-text] .dock__send{opacity:1;transform:none;visibility:visible;pointer-events:auto;
  transition:opacity var(--d-2) var(--ease-out),transform var(--d-3) var(--ease-spring),background-color var(--d-1) var(--ease-inout),visibility 0s}
.dock[data-has-text] .dock__mic{opacity:0;transform:scale(.4);visibility:hidden;pointer-events:none;
  transition:opacity var(--d-1) var(--ease-in),transform var(--d-2) var(--ease-in),visibility 0s linear var(--d-2)}
.dock__btn:focus-visible,.dock__plus:focus-visible,.dock__mic:focus-visible,.dock__send:focus-visible{border-radius:var(--r-full)}
.dock__hint{position:absolute;left:50%;bottom:calc(100% + var(--s-10));translate:-50% 0;padding:var(--s-8) var(--s-16);border-radius:var(--r-full);
  background:var(--toast);color:var(--toast-ink);font-size:var(--t-caption);white-space:nowrap;box-shadow:var(--sh-lg),inset 0 0 0 1px var(--toast-line);animation:rise var(--d-3) var(--ease-out)}
.dock__hint[hidden]{display:none}
/* 입력 바가 있는 앱 화면: 본문 끝이 입력 바에 가리지 않게. 입력 바 실제 높이(--dock-live)를 JS 가 주면 그걸 쓴다. */
.app--dock .app__body{padding-bottom:calc(var(--safe-b) + var(--tabbar-h) + var(--dock-live, var(--dock-h)) + var(--s-16))}
.app--no-tabbar.app--dock .app__body{padding-bottom:calc(var(--safe-b) + var(--dock-live, var(--dock-h)) + var(--s-16))}
/* 토스트는 입력 바 위에 뜬다 */
.app--dock:not(.app--no-tabbar) ~ .toasts{bottom:calc(var(--safe-b) + var(--tabbar-h) + var(--dock-live, var(--dock-h)) + var(--s-12))}
.app--dock.app--no-tabbar ~ .toasts{bottom:calc(var(--safe-b) + var(--dock-live, var(--dock-h)) + var(--s-12))}
