  :root {
    /* 기본 프라이머리 컬러 — 견적서 PDF 미리보기(print-v2.html)의 키컬러(네이비 #3156B7)로 통일 */
    --accent: #3156b7;
    --accent-dark: #2849a1;
    --accent-soft: rgba(49,86,183,0.08);
    --accent-rgb: 49,86,183;
    --bg: #eef0f3;
    --surface: #ffffff;
    --modal-bg: #f5f6f7;
    --surface-subtle: rgba(0,0,0,0.02);
    --border: rgba(0,0,0,0.10);
    --border-strong: rgba(0,0,0,0.20);
    --text: rgba(0,0,0,0.88);
    --text-sub: rgba(0,0,0,0.52);
    --text-muted: rgba(0,0,0,0.36);
    --danger: #ff3961;
    --shadow-card: 0 8px 56px rgba(0,0,0,0.03);
    --control-height: 32px;
  }
  * { box-sizing: border-box; }
  *::before, *::after { box-sizing: border-box; }
  body {
    margin: 0;
    font-family: 'Pretendard Variable', 'Pretendard', -apple-system, BlinkMacSystemFont, sans-serif;
    background: var(--bg);
    color: var(--text);
    font-size: 14px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
    /* [2026-08] 풋터(.app-footer)를 "고전적인 sticky footer" 패턴으로 고정하기 위한
       구조. body를 세로 flex 컨테이너로 만들고 #app에 flex:1을 줘서, 콘텐츠가 화면보다
       짧으면(화면이 아주 큰 모니터 등) #app이 남는 세로 공간을 전부 차지해 풋터가 항상
       브라우저 창 맨 아래로 밀려나고, 콘텐츠가 화면보다 길면(스크롤 필요) #app이 자기
       내용만큼 자연스럽게 커져서(flex-shrink:0이라 줄어들지 않음) 풋터는 그 스크롤의
       맨 끝에 위치한다 — height:100vh(고정)가 아니라 min-height:100vh를 써서 문서
       전체가 자연스럽게 스크롤되는 기존 구조(별도 내부 스크롤 컨테이너 없음)를 그대로
       유지한다. #loginScreen은 position:fixed라 이 flex 흐름과 무관. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
  }
  /* Desktop/Claude/Resource Manager (03-navigation-header-integrated.html)의
     .navbar/.logo/.nav-right와 동일한 값(높이 60px, 흰 배경, 로고/액션 좌우 32px 고정)으로 맞춤.
     이 화면은 리스트 하나뿐이라 스크롤이 생길 수 있어 position은 fixed 대신 sticky로 유지. */
  .navbar {
    position: sticky;
    top: 0;
    width: 100%;
    min-width: 1160px;
    height: 60px;
    border-bottom: 1px solid var(--border);
    z-index: 50;
    /* [2026-08] 기본 상태는 블러 없는 완전 불투명 화이트 — 페이지가 처음 열렸을 때
       (스크롤 전) 이미 블러가 적용된 채로 보이던 것을 고쳤다. 스크롤이 시작되는
       순간(#topbar에 JS가 붙이는 .is-scrolled, app.html의 스크롤 리스너 참고)에만
       아래 .navbar.is-scrolled 규칙의 값(반투명+블러)으로 0.3s 트랜지션된다. 두
       속성 다 transition에 명시해서, 다시 맨 위로 스크롤해 돌아갈 때도 대칭으로
       부드럽게 풀린다. */
    background: #ffffff;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    transition: background-color 0.3s ease, backdrop-filter 0.3s ease, -webkit-backdrop-filter 0.3s ease;
    /* 새견적서 모달(.form-modal-overlay, opacity 0→1 전환)이 열릴 때 이 sticky+backdrop-filter
       요소 바로 위를 덮으면서, 크롬이 이 레이어의 블러를 매 프레임 다시 합성하려다가 한두 프레임
       깜빡이는(dimmed 이중으로 보이는) 현상이 있어서 — will-change로 미리 별도 GPU 레이어로
       승격시켜 모달이 열려도 이 레이어 자체는 다시 합성할 필요가 없게 함. 이제 배경/블러 값
       자체도 스크롤마다 애니메이션되므로 이 레이어 승격이 이전보다 더 유효해졌다. */
    will-change: background-color, backdrop-filter;
  }
  /* [2026-08] Resource Manager의 LNB 헤더/본문과 동일한 프로스트 글래스 값(90% 불투명 +
     blur 24px) — 스크롤이 한 번이라도 시작되면(window.scrollY>0) 붙는 상태. 크롬
     devtools는 backdrop-filter/-webkit-backdrop-filter를 같은 속성의 별칭(alias)으로
     취급해서 "나중에 선언된 쪽이 이긴다"는 캐스케이드 규칙을 그대로 적용 — 표준 속성을
     먼저, webkit 접두사를 나중에 쓰면 표준 쪽이 "덮어써진 것처럼" 취소선으로 보이는데
     (값은 동일해서 실제 렌더링에는 차이 없음), 접두사를 먼저·표준을 나중에 쓰는 통상적인
     순서로 정리해서 표준 속성이 최종 적용되는 값으로 보이게 함. */
  .navbar.is-scrolled {
    -webkit-backdrop-filter: blur(24px);
    backdrop-filter: blur(24px);
    background: rgba(255,255,255,0.7);
  }
  .logo {
    position: absolute;
    left: 32px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    cursor: pointer;
  }
  .logo svg { display: block; }
  .nav-right {
    position: absolute;
    right: 32px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    gap: 12px;
  }
  /* Resource Manager의 .function-keys .divider와 동일한 값 */
  .nav-divider { width: 1px; height: 16px; background: var(--border); }
  .more-btn {
    all: unset;
    cursor: pointer;
    display: flex;
    padding: 4px;
  }
  .more-btn svg { display: block; width: 24px; height: 24px; color: rgba(0,0,0,0.6); }
  .more-btn.active svg { color: var(--accent); }
  /* Resource Manager의 .more-menu/.more-menu-email/.more-menu-item과 동일한 구성·모션
     (프로스트 글래스 + drop-fade). #moreMenuRoot는 .nav-right(transform 조상) 밖에 있어야
     position:fixed 좌표 계산이 뷰포트 기준으로 정확히 맞음(위 .nav-right 주석 참고). */
  .more-menu {
    position: fixed;
    z-index: 60;
    min-width: 120px;
    background: rgba(255,255,255,0.92);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: 0 8px 24px rgba(0,0,0,0.14);
    padding: 8px;
    opacity: 0;
    transform: translateY(-5px);
    transition: transform 0.1s ease, opacity 0.1s ease;
    /* .period-calendar-popover와 동일한 이유 — 이 클래스를 공유하는 .custom-select-menu가
       새견적서 모달의 dimmed 위에서 열릴 때(등급/파트/반올림단위 드롭다운 등)도 같은
       backdrop-filter 재합성 깜빡임이 있을 수 있어 미리 방지. */
    will-change: opacity, transform;
  }
  .more-menu-email {
    padding: 8px 10px 10px;
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    border-bottom: 1px solid var(--border);
    margin-bottom: 4px;
  }
  .more-menu-item {
    all: unset;
    box-sizing: border-box;
    width: 100%;
    display: block;
    padding: 8px 6px 8px 10px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
  }
  .more-menu-item:hover { background: var(--accent-soft); }
  /* 폭 하한/상한을 필터/리스트 카드 각각이 아니라 #app 하나에만 두는 구조로 정리 —
     예전엔 #app도 max-width:1300을 갖고 그 자식(list-wrap)도 별도로 min/max
     1040/1440을 가져서 "두 군데서 서로 다른 숫자로 캡을 거는" 상태였음.
     여기 숫자(1104/1504)는 list-wrap이 원래 갖고 있던 1040/1440에 #app 자신의
     좌우 패딩(32px×2=64px)을 그대로 더한 값이다 — 전체 요소가 border-box라 폭
     지정이 padding까지 포함해서 계산되므로, 이 패딩만큼 보정해주지 않으면(그냥
     1040/1440을 그대로 썼다면) #app 패딩 64px가 새로 한 겹 더 끼어들어서 표가
     실제로 쓸 수 있는 폭이 원래 설계보다 64px 더 좁아져버린다(카드 패딩은
     이미 그 1040/1440 숫자 안에 포함되어 있었기 때문). 이렇게 보정하면 표가
     실제로 갖는 최소/최대 폭은 예전과 정확히 동일하게 유지된다. */
  #app {
    /* flex-grow:1(남는 공간 흡수) + flex-shrink:0(내용보다 작게는 안 줄어듦) —
       위 body의 sticky footer 설명 참고.
       [2026-08 버그수정] margin-left/right:auto가 flex의 교차축 stretch를 무효화하는
       건 맞는데(가운데 정렬 자체는 의도대로 됨), 그 대신 폭 계산 알고리즘 자체가
       "부모 폭을 꽉 채운 뒤 min/max로 클램프"에서 "내용물 크기에 맞춰 줄어드는(shrink-
       to-fit)" 방식으로 바뀌어버렸다 — 그래서 견적명이 짧은 페이지일수록 표 내용이
       작아지고, 그 좁아진 내용 크기 그대로 카드 전체 폭이 줄어드는 버그가 생겼음
       (min-width가 있어도 이 shrink-to-fit 계산이 그보다 먼저 작용). width:100%를
       명시해서 예전(일반 block 요소)과 동일하게 "우선 꽉 채우고, 그 다음에만 min/
       max-width로 클램프"하도록 강제 — 뷰포트가 max-width보다 넓을 때만 100%가
       1440px로 눌리면서 남는 공간이 좌우 auto margin으로 가 가운데 정렬되고, 좁을
       때는 min-width가 100%보다 우선해 1160px 밑으로는 안 줄어드는, 원래 의도한
       동작으로 복원됨. */
    width: 100%;
    flex: 1 0 auto;
    min-width: 1160px;
    max-width: 1440px;
    margin: 32px auto 24px;
    padding: 0 32px;
  }
  /* [2026-08] 화면 최하단 저작권 표기. #app이 innerHTML을 통째로 다시 그리는
     구조라(render 함수들), 그 안이 아니라 형제(app.html의 </div id="app"> 바로
     다음)로 둬서 화면이 바뀌어도 계속 살아있게 한다. 위 #app의 margin-bottom(48px)이
     콘텐츠와 이 풋터 사이의 간격을 만든다. */
  .app-footer {
    flex-shrink: 0;
    padding: 20px 0;
    border-top: 1px solid rgba(0,0,0,0.1);
    text-align: center;
    font-size: 12px;
    font-weight: 600;
    line-height: 18px;
    letter-spacing: 1px;
    color: rgba(0,0,0,0.4);
  }
  h1, h2, h3 { margin: 0; }
  h1 { font-size: 24px; line-height: 34px; letter-spacing: -0.4px; }
  h2 { font-size: 20px; line-height: 28px; letter-spacing: -0.2px; }
  .card {
    background: var(--surface);
    border: none;
    border-radius: 16px;
    padding: 26px 32px 32px;
    margin-bottom: 16px;
    box-shadow: var(--shadow-card);
  }
  .card > h2 { margin-bottom: 20px; }
  .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
  .grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
  .rate-settings-grid {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 16px;
  }
  /* 제경비율/기술료율 폼을 개발비용/기타 부대비용 표의 필드(field-input-cell)와 같은 32px 높이로 맞춤 */
  .rate-settings-grid input[type="number"] { height: 32px; }
  /* 작성 중 총 MM을 바로 확인할 수 있도록 그 행의 우측 끝(6번째 칸)에 배치.
     제경비율/기술료율의 인풋(라벨 아래쪽)과 밑선이 맞도록 폼이 아니라 일반 텍스트로 두고
     grid item 자체를 아래쪽으로 정렬(align-self:end)함. */
  .rate-settings-grid .total-mm-field {
    grid-column: 6 / 7;
    align-self: end;
    text-align: right;
    font-size: 13px;
    color: var(--text-sub);
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    white-space: nowrap;
  }
  .rate-settings-grid .total-mm-field .total-mm-num {
    font-weight: 700;
    color: var(--accent);
    font-variant-numeric: tabular-nums;
    margin: 0 3px;
  }
  .content-card-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
  }
  .content-card .field:last-child { margin-bottom: 0; }
  /* 업무내용/기타사항(수행범위/개발산출물/결제조건/기타내용) textarea 텍스트 크기만 별도로 관리 */
  .bullet-textarea { font-size: 13px; }
  .form-page-header {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 24px;
    margin-bottom: 24px;
  }
  .form-page-header .eyebrow {
    margin-bottom: 4px;
    color: var(--accent);
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    letter-spacing: 0.4px;
  }
  .form-page-header .page-description {
    margin: 6px 0 0;
    color: var(--text-sub);
    font-size: 13px;
    line-height: 20px;
  }
  .form-section-header {
    padding-bottom: 18px;
    margin-bottom: 22px;
    border-bottom: 1px solid var(--border);
  }
  .form-section-header h2 { margin: 0; }
  .form-section-header p {
    margin: 4px 0 0;
    color: var(--text-sub);
    font-size: 14px;
    line-height: 20px;
  }
  .basic-info-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 16px;
    row-gap: 16px;
  }
  .field-span-2 { grid-column: span 2; }
  .field-span-1 { grid-column: span 1; }

  /* ==== 폼 클래스 네이밍 체계 정리(전체 지도) ====
     이 앱의 폼 종류를 6개 계열로 통일함 — 어떤 화면(필터/리스트/작성-수정모달)에
     있든 같은 종류면 항상 같은 클래스를 쓰므로, 종류별로 딱 한 군데만 고치면 됨.
       1) 일반 인풋   .field-input           (카드형, 단답형 텍스트/숫자)
       2) 표 셀 인풋  .field-input-cell(-text/-center/-right)  (개발비용/기타비용 표 안)
       3) 검색폼     .field-search / .field-search-icon
       4) 날짜입력폼 .period-date-trigger + .period-date-field/.filter-date-field
       5) 드랍다운폼 .period-date-trigger(베이스 재사용) + .custom-select-trigger
       6) 체크박스   .field-checkbox(-group/-icon)
       (+ 선택카드  .field-choice-group/.field-choice(-icon/-ring/-dot) — 견적유형처럼
        텍스트/아이콘 트리거와는 결이 다른 카드형 선택 UI)
     4)/5)는 시각적으로 거의 같아서(둘 다 "입력창처럼 생긴 버튼") 이름은 다르지만
     실제로는 .period-date-trigger 하나를 공유하는 베이스+modifier 구조다 — 이미
     충분히 통일돼 있고, 여기저기(달력 popover 위치 찾기, 바깥 클릭 감지 등) JS
     동작이 이 정확한 클래스명에 깊게 의존하고 있어서 이름 자체는 그대로 두고
     이 주석으로만 소속을 분명히 해둠. */
  .period-date-field { position: relative; }
  .period-date-trigger {
    all: unset;
    box-sizing: border-box;
    cursor: pointer;
    width: 100%;
    height: var(--control-height);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 4px 6px 4px 10px;
    border: 1px solid rgba(0,0,0,0.2);
    border-radius: 8px;
    background: #fff;
    color: rgba(0,0,0,0.8);
    font-family: 'Pretendard Variable', 'Pretendard', sans-serif;
    font-size: 13px;
    line-height: 16px;
    font-weight: 400;
    text-align: left;
    transition: background 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
  }
  .period-date-trigger:hover {
    background: rgba(var(--accent-rgb),0.06);
    border-color: var(--accent);
    box-shadow: inset 0 0 0 0.5px var(--accent);
  }
  .period-date-trigger.active {
    border-color: var(--accent);
    background: rgba(var(--accent-rgb),0.06);
    box-shadow: inset 0 0 0 0.5px var(--accent);
  }
  .period-date-trigger span {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: 16px;
    font-variant-numeric: tabular-nums;
  }
  .period-date-trigger .placeholder { color: rgba(0,0,0,0.35); }
  .period-date-trigger svg { width: 20px; height: 20px; flex: 0 0 auto; color: rgba(0,0,0,0.4); }

  /* 커스텀 드롭다운(셀렉트) — .period-date-trigger(입력창처럼 생긴 버튼)를 그대로
     재사용하되 아이콘만 캘린더 대신 셰브런으로 바꿔서, 네이티브 <select>를 대체한다.
     메뉴/옵션 목록은 "더보기(⋯)" 메뉴(.more-menu/.more-menu-item)를 그대로 재사용. */
  .custom-select-trigger .select-chevron-icon { width: 16px; height: 16px; }
  /* .more-menu의 기본 z-index(60)는 견적 작성/수정 모달(.form-modal-overlay, z-index:150)보다
     낮아서, 모달 안(개발비용 파트/등급, 반올림 단위)의 드롭다운이 모달에 가려 안 보이는 문제가
     있었음 — 모달보다는 높고 등급변경 확인 팝업(z-index:250)보다는 낮게 잡음. */
  .custom-select-menu { max-height: 260px; overflow-y: auto; z-index: 200; }
  .more-menu-item.custom-select-option {
    display: flex; align-items: center; justify-content: space-between; gap: 4px;
  }
  .custom-select-option .check-icon { display: none; width: 14px; height: 14px; flex: 0 0 auto; color: var(--accent); }
  .custom-select-option.selected { color: var(--accent); font-weight: 600; }
  .custom-select-option.selected .check-icon { display: inline-flex; }
  /* 개발비용 표 안의 파트/기술등급 셀렉트는 폭이 좁은 칸이라 크기만 좀 더 컴팩트하게 —
     정렬 자체는 다른 폼의 트리거(.period-date-trigger)와 동일하게 텍스트 좌측/아이콘 우측 끝 유지
     (justify-content: space-between은 기본 .period-date-trigger 규칙에서 그대로 물려받음). */
  .item-table .custom-select-trigger { gap: 4px; padding: 4px 6px 4px 10px; }
  .item-table .custom-select-trigger .select-chevron-icon { width: 14px; height: 14px; }
  /* 리스트 툴바의 "페이지당 개수" 트리거는 원래 네이티브 select 크기(32px, 13px)와 맞춤 */
  .list-toolbar .custom-select-trigger {
    width: auto; flex: 0 0 auto; height: 32px; padding: 4px 6px 4px 10px; font-size: 13px;
  }
  .period-calendar-popover {
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    z-index: 80;
    width: 232px;
    padding: 8px;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 10px;
    background: rgba(255,255,255,0.86);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.14);
    opacity: 0;
    transform: translateY(-5px);
    transition: transform 0.2s ease, opacity 0.2s ease;
    /* 새견적서 모달의 어두운 dimmed(.form-modal-overlay) 위에서 이 프로스트 글래스 팝오버가
       나타날 때, backdrop-filter가 매 프레임 다시 합성되며 순간적으로 더 어둡게/이중으로
       보이는 깜빡임이 있어서 — will-change로 미리 별도 레이어로 승격시켜 완화. */
    will-change: opacity, transform;
  }
  .period-calendar-popover.visible { opacity: 1; transform: translateY(0); }
  .client-field { position: relative; }
  .client-autocomplete-popover {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 80;
    max-height: 220px;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid rgba(0,0,0,0.1);
    border-radius: 10px;
    background: rgba(255,255,255,0.86);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
    box-shadow: 0 8px 24px rgba(0,0,0,0.14);
    opacity: 0;
    transform: translateY(-5px);
    transition: transform 0.2s ease, opacity 0.2s ease;
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* .period-calendar-popover와 동일한 이유(backdrop-filter 재합성 깜빡임 완화). */
    will-change: opacity, transform;
  }
  .client-autocomplete-popover.visible { opacity: 1; transform: translateY(0); }
  .client-suggestion-item {
    all: unset;
    display: block;
    flex: 0 0 auto;
    box-sizing: border-box;
    cursor: pointer;
    width: 100%;
    padding: 6px 10px;
    border-radius: 6px;
    font-size: 13px;
    line-height: 20px;
    color: rgba(0,0,0,0.8);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .client-suggestion-item:hover { background: rgba(var(--accent-rgb),0.1); }
  .cal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
  .cal-label {
    font-family: 'JetBrains Mono', 'SF Mono', ui-monospace, monospace;
    font-size: 12px;
    font-weight: 700;
    color: #000;
    text-transform: uppercase;
  }
  .cal-nav {
    all: unset;
    box-sizing: border-box;
    cursor: pointer;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
    color: rgba(0,0,0,0.5);
    font-size: 13px;
  }
  .cal-nav:hover { background: rgba(0,0,0,0.06); color: #000; }
  .cal-weekdays, .cal-days { display: grid; grid-template-columns: repeat(7, 1fr); }
  .cal-weekdays span {
    padding: 4px 0;
    color: rgba(0,0,0,0.35);
    font-family: 'JetBrains Mono', 'SF Mono', ui-monospace, monospace;
    font-size: 11px;
    text-align: center;
  }
  .cal-day {
    all: unset;
    box-sizing: border-box;
    cursor: pointer;
    padding: 6px 0;
    border-radius: 6px;
    color: #000;
    font-size: 12px;
    text-align: center;
  }
  .cal-day:hover { background: rgba(var(--accent-rgb),0.1); }
  .cal-day.selected { background: var(--accent); color: #fff; font-weight: 600; }
  .cal-day.empty { cursor: default; }
  .cal-day.empty:hover { background: none; }
  .cal-day.disabled { color: rgba(0,0,0,0.2); cursor: not-allowed; }
  .cal-day.disabled:hover { background: none; }
  label {
    display: block;
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    margin-bottom: 4px;
    color: var(--text-sub);
    padding-left: 4px;
  }
  /* ==== 일반 인풋(.field-input) ====
     기존엔 태그 선택자(input[type=...])만으로 스타일을 공유하고 있어서 "이게 하나의
     통일된 폼 종류"라는 게 클래스명으로는 안 드러났음. .field-input을 태그 선택자
     옆에 나란히 추가해서, 앞으로는 이 클래스 하나만 보고도 "일반 인풋 폼"임을 알 수
     있고 여기 한 곳만 고치면 됨(태그 선택자는 혹시 클래스 부여를 놓친 인풋이 있어도
     안전하게 같은 스타일을 받도록 남겨둔 안전망). select는 커스텀 드롭다운
     (.custom-select-trigger)으로 전부 교체돼서 이제 실제로 매칭되는 <select>가
     없지만, 실수로 남아있을 경우를 위한 안전망으로 계속 남겨둠. */
  input[type=text], input[type=number], input[type=date], textarea, select, .field-input {
    width: 100%;
    min-width: 0;
    font-family: inherit;
    font-size: 13px;
    line-height: 16px;
    /* --control-height 36→32px 축소에 맞춰 세로 padding도 10→8px로 같이 줄임
       (글자 들어갈 실제 높이 비율을 그대로 유지 — 안 줄이면 32px 안에서 글자가
       위아래로 눌려 보임). 좌우 12px은 그대로. */
    padding: 4px 6px 4px 10px;
    border: 1px solid rgba(0,0,0,0.2);
    border-radius: 8px;
    outline: none;
    background: #fff;
    color: rgba(0,0,0,0.9);
    transition: background 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
  }
  input[type=text], input[type=number], input[type=date], select, .field-input {
    height: var(--control-height);
    border-radius: 8px;
  }
  select {
    -webkit-appearance: none;
    appearance: none;
    padding: 8px 32px 8px 12px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' fill='none'%3E%3Cpath d='M4 6L8 10L12 6' stroke='%23666' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    background-size: 16px 16px;
  }
  input[type=text]:hover, input[type=number]:hover, input[type=date]:hover, textarea:hover, select:hover, .field-input:hover {
    background-color: rgba(var(--accent-rgb),0.06);
    border-color: var(--accent);
    box-shadow: inset 0 0 0 0.5px var(--accent);
  }
  input[type=text]:focus, input[type=number]:focus, input[type=date]:focus, textarea:focus, select:focus, .field-input:focus {
    background-color: rgba(var(--accent-rgb),0.06);
    border-color: var(--accent);
    box-shadow: inset 0 0 0 0.5px var(--accent);
  }
  input[readonly] {
    border-color: rgba(0,0,0,0.3);
    background: rgba(0,0,0,0.035);
    color: rgba(0,0,0,0.55);
    cursor: not-allowed;
  }
  input[readonly]:hover, input[readonly]:focus {
    border-color: rgba(0,0,0,0.3);
    background: rgba(0,0,0,0.035);
    box-shadow: none;
  }
  input:disabled {
    opacity: 1;
    border-color: rgba(0,0,0,0.14);
    background: rgba(0,0,0,0.035);
    color: rgba(0,0,0,0.72);
    cursor: default;
    -webkit-text-fill-color: rgba(0,0,0,0.72);
  }
  input:disabled:hover,
  input:disabled:focus {
    border-color: rgba(0,0,0,0.14);
    background: rgba(0,0,0,0.035);
    box-shadow: none;
  }
  input::placeholder, textarea::placeholder { color: var(--text-muted); }
  textarea { resize: none; min-height: 64px; line-height: 1.5; }
  .field { min-width: 0; margin-bottom: 14px; }
  .basic-info-grid .field { margin-bottom: 0; }
  .hint { font-size: 11px; line-height: 16px; color: var(--text-muted); margin-top: 5px; }

  button {
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    min-height: 36px;
    min-width: 100px;
    padding: 8px 16px;
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,0.3);
    background: #fff;
    color: var(--text);
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  }
  button:hover { background: #f0f1f3; border-radius: 8px;}
  button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
  button.primary:hover { background: var(--accent-dark); }
  button.danger { color: var(--danger); border-color: rgba(192,57,43,0.4); }
  button.ghost { background: transparent; border-color: transparent; }
  /* 3버튼 확인모달의 "덮어쓰기"처럼 남색이되 solid(.primary)만큼 강한 강조는 필요 없는
     보조 확정 액션용 — 흰 배경에 남색 테두리/텍스트만 준다. */
  button.accent-outline { background: #fff; border: 1px solid var(--accent); color: var(--accent); }
  button.accent-outline:hover { background: var(--accent-soft); border: 1.5px solid var(--accent); }
  /* 표 안 행 단위 버튼(수정/복제/삭제 등)처럼 컴팩트해야 하는 버튼은 기본 min-width에서 빠짐 */
  button.small { padding: 5px 10px; font-size: 12px; min-width: 0; }
  button:disabled { opacity: 0.3; cursor: not-allowed; }
  /* "+" 텍스트 대신 아이콘(Assets/add.svg)을 쓰는 버튼/라벨 공용(새 견적 작성, 항목 추가 등).
     좌우 패딩을 비대칭으로 쓰는 건 아이콘+텍스트 조합에서만 필요해서, 일반 버튼(취소/저장 등)과
     같은 규칙을 공유하지 않도록 이 클래스 전용으로 분리(button 요소일 때만 패딩 오버라이드). */
  .btn-icon-label { display: inline-flex; align-items: center; gap: 4px; vertical-align: middle; }
  .btn-icon-label svg { flex: 0 0 auto; }
  button.btn-icon-label { padding: 8px 16px 8px 8px; min-width: 0; }

  table { width: 100%; border-collapse: collapse; font-size: 13px; }
  /* 견적서작성모달의 개발비용/기타부대비용 표 헤더(.item-table th/.item-table thead th)와
     동일한 스타일로 맞춤(회색 배경, 12px, 6px 8px 패딩, box-shadow 기반 위/아래 보더). */
  thead th {
    position: relative;
    text-align: center;
    font-size: 12px;
    color: var(--text);
    background: #eef0f3;
    padding: 6px 8px;
    white-space: nowrap;
    border-top: none;
    border-bottom: none;
    box-shadow: inset 0 1px 0 0 var(--border), inset 0 -1px 0 0 var(--border);
  }
  /* print의 "파트 | 주요업무내역 | 기술등급" 헤더처럼 셀 사이 구분선(divider) 추가.
     border-right는 셀 높이 전체(위아래 끝까지)를 꽉 채워버려 print의 짧은 v-divider와
     느낌이 달라서, 대신 위아래에 여백을 준 ::after 선으로 대체(헤더 패딩과 같은 6px만큼 인셋). */
  thead th:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 6px;
    bottom: 6px;
    right: 0;
    width: 1px;
    background: var(--border);
  }
  tbody td {
    padding: 10px;
    vertical-align: middle;
    text-align: left;
  }
  /* print-v2.html과 동일하게 흰/회 번갈아 배경(짝수 행이 #f5f6f7) */
  tbody tr:nth-child(odd) { background: #ffffff; }
  tbody tr:nth-child(even) { background: #f5f6f7; }
  tbody tr:hover { background: #fafbfc; }
  /* [2026-08] 흰/회 번갈아 배경 표는 기본적으로 행 사이에 선을 긋지 않고 배경 대비만으로
     구분한다. 다만 표가 흰색(홀수) 행으로 끝나버리면 배경만으로는 "여기서 표가 끝났다"는
     경계가 잘 안 보여서 그 경우에만 마지막 행 아래에 선을 하나 그어준다(회색(짝수) 행으로
     끝나면 배경 자체가 이미 경계 역할을 하므로 선이 필요 없음). 표 바로 아래에 다른 요소가
     곧장 이어붙어서 그 자체로 경계가 되는 경우(예: item-table의 "+ 항목 추가" 줄처럼 마지막
     행이 이미 자기만의 테두리를 갖는 경우)는 그 요소의 더 구체적인 선택자가 이 규칙을
     덮어써서 자동으로 제외된다. */
  tbody tr:last-child:nth-child(odd) { border-bottom: 1px solid var(--border); }
  /* 랜딩 리스트는 더 이상 <table>이 아니라 CSS Grid 기반(.list-grid/.list-row/.list-cell)
     으로 구현 — <table>+table-layout:fixed로 "견적명 칸만 width 미지정 → 남는 공간 자동
     흡수"를 시도했다가, 이 환경의 fixed-layout 구현이 그 칸을 통째로 0으로 무너뜨리는
     문제를 겪었고(그 다음 모든 칸에 px를 줘서 회피했지만, 그러면 "칸 전체가 비율대로
     같이 늘어남"이 되어 "견적명만 가변, 나머지는 고정"은 만족을 못함) — CSS Grid의 1fr
     트랙은 정확히 "나머지 칸은 고정 폭 그대로, 이 칸만 남는 공간을 전부 흡수"를 스펙으로
     보장하는 별개의(더 안정적인) 메커니즘이라 <table> 자체를 걷어내고 이걸로 교체함.
     칸 폭은 .list-row 한 곳에서만 선언해서 헤더/본문/필러 행이 항상 같은 값을 공유(정렬
     보장). item-table(개발비용/기타부대비용)은 실제 <table>을 그대로 쓰므로 위쪽의
     table/thead th/tbody td 등 공용 규칙은 그대로 둠 — 이 블록만 랜딩 리스트 전용. */
  .list-grid { display: flex; flex-direction: column; }
  .list-row {
    display: grid;
    /* 등록번호 / 견적명(가변) / 고객사 / 작성일 / 작성자 / 유형 / 총견적금액 / 최종수정 / 더보기
       [2026-08] 리비전(과거 버전) 히스토리는 별도 칼럼(펼치기 꺽쇠) 대신 견적명 옆의
       작은 "REV" 알약 표시로만 안내 — 리스트에서 직접 펼쳐보는 기능은 만들지 않고
       (행 클릭=최신본 열람이라는 단일한 의미를 유지하기 위해) 과거 버전 열람 자체는
       print.html의 리비전 선택 드롭다운으로 넘김. */
    /* [2026-08 버그수정] 1fr 트랙은 스펙상 기본 최소폭이 auto(=내용의 min-content 크기)라서,
       견적명이 줄바꿈 없는 아주 긴 텍스트일 때 그 전체 폭만큼 트랙이 강제로 늘어나 카드/
       레이아웃 전체가 넓어지는 문제가 있었음(=흔히 "grid blowout"이라 불리는 함정 — .list-cell/
       .list-title-text에 이미 있던 overflow:hidden+ellipsis는 트랙 자체의 크기 계산에는
       영향을 못 줌, 박스가 먼저 정해진 뒤에야 그 안에서 잘라내는 것이기 때문). minmax(0, 1fr)로
       바꿔서 이 트랙의 최소폭을 0으로 명시하면 절대 남는 공간 이상으로 안 늘어나고, 대신 안의
       텍스트가 정상적으로 말줄임(…) 처리된다. */
    grid-template-columns: 96px minmax(0, 1fr) 120px 120px 96px 84px 132px 132px 56px;
  }
  .list-cell {
    display: flex;
    align-items: center;
    padding: 0 10px;
    box-sizing: border-box;
    font-size: 13px;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .list-cell.col-center { justify-content: center; text-align: center; }
  /* 이 리스트 표 안에서 .num 클래스는 실제로 "총 견적금액" 셀 하나에만 쓰여서(다른 값들은
     .mono만 공유) 여기 font-weight를 줘도 등록번호/작성일/최종수정(.mono)에는 안 번짐. */
  .list-cell.num { justify-content: flex-end; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
  /* 등록번호/작성일/총견적금액/최종수정은 폰트는 그대로 Pretendard를 쓰되, 숫자 글리프만
     자릿수와 무관하게 항상 같은 폭으로 렌더되도록 tabular-nums만 적용 */
  .list-cell.mono { font-variant-numeric: tabular-nums; }

  /* 헤더 셀(thead th와 동일한 톤: 회색 배경, 12px, 6px 8px 패딩, 위/아래 인셋 보더) */
  .list-header .list-cell {
    position: relative;
    padding: 6px 8px;
    font-size: 12px;
    font-weight: 600;
    background: #eef0f3;
    box-shadow: inset 0 1px 0 0 var(--border), inset 0 -1px 0 0 var(--border);
  }
  /* 값(본문)은 우측정렬이어도 헤더는 다른 헤더들과 통일감 있게 중앙정렬 */
  .list-header .list-cell.num { justify-content: center; text-align: center; }
  .list-header .list-cell:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 8px;
    bottom: 8px;
    right: 0;
    width: 1px;
    background: var(--border);
  }
  .list-header .list-cell.sortable {
    cursor: pointer;
    user-select: none;
    gap: 4px;
  }
  .list-header .list-cell.sortable:hover { background: rgba(0,0,0,0.05); }
  .sort-icon { display: inline-flex; align-items: center; color: rgba(0,0,0,0.25); flex: 0 0 auto; }
  .sort-icon svg { width: 10px; height: 10px; display: block; }
  .sort-icon.active { color: var(--accent); }

  /* 페이지당 표시 개수에 맞춰 표 높이를 고정하기 위한 빈(필러) 행 포함, 모든 본문 행에
     공통 최소 높이를 줘서 — 내용이 텍스트 한 줄이든 비어있든 항상 똑같은 키를 갖게 함
     (이전엔 "필러 행이 액션 버튼 칸의 실제 높이와 정확히 같아야 한다"는 식으로 셀 하나의
     치수에 맞춰 유지보수해야 했는데, 행 전체에 공통 최소높이를 주는 이 방식이 훨씬 단순함). */
  .list-body .list-row { min-height: 48px; }
  .list-body .list-row:not(.filler-row) { cursor: pointer; }
  /* print-v2.html과 동일하게 흰/회 번갈아 배경(짝수 행이 #f5f6f7). :nth-child(odd/even)
     대신 실제 데이터 배열 순서(i)로 직접 계산한 클래스를 쓴다(buildTable() 참고) —
     나중에 이 행들 사이에 다른 요소가 끼어들 일이 생겨도(DOM 위치 기반인 nth-child와
     달리) 줄무늬가 절대 영향받지 않도록 미리 안전하게 만들어둔 것. */
  .list-body .list-row.row-white { background: #ffffff; }
  .list-body .list-row.row-gray { background: #f5f6f7; }
  .list-body .list-row:hover { background: rgba(var(--accent-rgb),0.15); }
  /* [2026-08] item-table/admin-table(진짜 <table>)에 넣은 "흰색 마지막 행일 때만 하단선"
     규칙과 같은 정책을 여기(랜딩 리스트, <table> 아니라 div+grid라 tbody/tr 기반 규칙이
     안 먹힘)에도 동일하게 적용. 처음엔 :last-child로 판단하려 했는데, 페이지 끝을 채우는
     빈 필러 행(filler-row, buildTable() 참고)이 뒤에 더 붙으면 DOM상 진짜 마지막 자식이
     그 필러 행이 되어버려서 실제 데이터가 끝나는 자리를 못 찾는 문제가 있었음 — 그래서
     :last-child 대신, 실제 마지막 데이터 행에 buildTable()이 직접 붙여주는 .last-real-row
     클래스로 판단하도록 변경(필러 유무와 완전히 무관하게 항상 정확한 위치에 선이 그려짐). */
  .list-body .list-row.row-white.last-real-row { border-bottom: 1px solid var(--border); }
  /* 필러(빈 데이터 채우기) 행은 실제 데이터가 아니므로 줄무늬/호버 없이 항상 흰 배경 */
  .list-body .list-row.filler-row { background: #ffffff; }
  .list-body .list-row.filler-row .list-cell { grid-column: 1 / -1; }

  /* 리스트(표) 로딩 스켈레톤 — Resource Manager(#loadingSkeleton)와 동일한 shimmer
     애니메이션을 재사용, .list-row와 같은 줄무늬 톤으로 표의 대략적인 칼럼 비율을 흉내냄.
     필터 카드는 이 로딩과 무관하게 이미 떠 있으므로(renderList() 참고), 이건 #list-wrap
     안(표 자리)에만 들어감. */
  .skeleton-list { display: flex; flex-direction: column; }
  /* .list-row와 정확히 동일한 grid-template-columns를 재사용 — 예전엔 flex+gap+퍼센트폭을
     섞어서 만들었다가, 고정폭 칼럼들의 합계 + 퍼센트 칼럼이 같은 컨테이너 기준으로 중복
     계산되면서 카드 폭보다 넓어져 오른쪽/아래로 넘치던 문제가 있었음(flex 아이템에
     flex-shrink:0을 줘서 줄어들지도 못했음) — 실제 표처럼 grid의 1fr 트랙이 "남는 공간만"
     정확히 흡수하는 방식으로 바꿔서 폭 초과 자체가 구조적으로 불가능하게 함. */
  .skeleton-list-row {
    display: grid;
    /* [2026-08 버그수정] 1fr 트랙은 스펙상 기본 최소폭이 auto(=내용의 min-content 크기)라서,
       견적명이 줄바꿈 없는 아주 긴 텍스트일 때 그 전체 폭만큼 트랙이 강제로 늘어나 카드/
       레이아웃 전체가 넓어지는 문제가 있었음(=흔히 "grid blowout"이라 불리는 함정 — .list-cell/
       .list-title-text에 이미 있던 overflow:hidden+ellipsis는 트랙 자체의 크기 계산에는
       영향을 못 줌, 박스가 먼저 정해진 뒤에야 그 안에서 잘라내는 것이기 때문). minmax(0, 1fr)로
       바꿔서 이 트랙의 최소폭을 0으로 명시하면 절대 남는 공간 이상으로 안 늘어나고, 대신 안의
       텍스트가 정상적으로 말줄임(…) 처리된다. */
    grid-template-columns: 96px minmax(0, 1fr) 120px 120px 96px 84px 132px 132px 56px;
    align-items: center;
    min-height: 48px;
    padding: 0 10px;
    box-sizing: border-box;
  }
  .skeleton-list-row:nth-child(odd) { background: #ffffff; }
  .skeleton-list-row:nth-child(even) { background: #f5f6f7; }
  .skeleton-block {
    height: 14px;
    width: 70%;
    max-width: 100%;
    border-radius: 6px;
    background: linear-gradient(90deg, rgba(0,0,0,0.05) 25%, rgba(0,0,0,0.09) 37%, rgba(0,0,0,0.05) 63%);
    background-size: 400% 100%;
    animation: skeleton-shimmer 1.4s ease infinite;
  }
  @keyframes skeleton-shimmer {
    0% { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
  }

  /* 자주 안 쓰는 수정/복제/삭제는 행마다 늘 펼쳐두지 않고 더보기(⋯) 버튼 뒤로 숨김
     (Navigation의 .more-btn과 동일한 스타일/모션). 행 자체를 누르면 보기/PDF와 동일하게
     열리므로, 더보기 버튼 클릭이 그 행 클릭으로 번지지 않도록 app.html의 toggleRowMenu()가
     stopPropagation 처리함. */
  .list-cell.actions-cell { justify-content: center; overflow: visible; white-space: normal; }
  .row-more-btn {
    all: unset;
    box-sizing: border-box;
    cursor: pointer;
    display: flex;
    padding: 4px;
    border-radius: 6px;
  }
  .row-more-btn svg { display: block; width: 18px; height: 18px; color: rgba(0,0,0,0.5); }
  .row-more-btn:hover, .row-more-btn.active { background: rgba(0,0,0,0.06); }
  .row-more-btn:hover svg, .row-more-btn.active svg { color: #000; }
  /* 더보기 메뉴 자체는 Navigation의 .more-menu/.more-menu-item을 그대로 재사용(id만 다름:
     nav는 #moreMenu, 리스트 행은 #rowMenu) — 삭제 항목만 위험색 강조가 필요해서 추가 */
  .more-menu-item.danger { color: var(--danger); }
  .more-menu-item.danger:hover { background: rgba(192,57,43,0.08); }

  /* [2026-08] 견적명 셀 — 제목 텍스트만 말줄임되고(긴 제목이어도 REV 배지는 항상
     온전히 보이도록 min-width:0으로 flex 자식이 실제로 줄어들 수 있게 함) 리비전이
     있는 견적서만 옆에 회색 알약("REV")을 붙임. 리스트에서 직접 과거 버전을 펼쳐보는
     기능은 만들지 않음 — 행 클릭이 "최신본 열람" 하나의 의미만 갖도록 유지하고, 실제
     과거 버전 열람은 print.html의 리비전 선택 드롭다운에서 처리(예전엔 펼치기 꺽쇠를
     행 안에 같이 두려다가 행 클릭과 의미가 충돌해 UX가 꼬였음). */
  .list-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; font-weight: 500;}
  .rev-badge {
    flex: 0 0 auto;
    margin-left: 6px;
    padding: 2px 8px;
    border-radius: 999px;
    background: rgba(0,0,0,0.1);
    color: rgba(0,0,0,0.4);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.02em;
  }

  /* 리스트 카드: 칸 폭 고정 합계(836px)+견적명 최소 여유+카드 좌우 패딩(32px*2)을 감안한
     하한선/상한선은 이제 #list-wrap이 아니라 #app에 있음(위 #app 규칙 참고) — 그래야
     같은 #app 아래에 있는 #filter-wrap도 항상 같은 폭을 그대로 물려받는다. */

  .item-table { width: 100%; border-collapse: collapse; margin-bottom: 8px; }
  .item-table th {
    position: relative;
    background: #eef0f3; font-size: 12px; padding: 6px 8px; text-align: center;
  }
  /* print의 헤더 구분선과 같은 패턴: 셀 사이에만 세로선(마지막 칸/삭제 아이콘 칸 앞은 생략).
     위아래 여백이 있는 짧은 선이라 border가 아니라 ::after로 인셋 처리(위 thead th와 동일 이유). */
  .item-table th:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 6px;
    bottom: 6px;
    right: 0;
    width: 1px;
    background: var(--border);
  }
  /* 위/아래 보더 둘 다 유지하되, border-collapse가 "아래쪽(테이블 안쪽, tbody와 맞닿는 면)"
     선은 옆 셀과 겹쳐서 계산하고 "위쪽(테이블 바깥쪽 끝)"은 혼자 계산하는 식이라, 같은 두께/색인데도
     안쪽 선이 미묘하게 더 진하게 렌더링되는 경우가 있음 — border 대신 box-shadow(inset)로 위/아래
     선을 그리면 옆 셀과의 병합 계산 자체를 안 타서 둘 다 완전히 동일한 두께/색으로 렌더링됨. */
  .item-table thead th {
    border-top: none;
    border-bottom: none;
    box-shadow: inset 0 1px 0 0 var(--border), inset 0 -1px 0 0 var(--border);
  }
  .item-table td { padding: 6px; vertical-align: middle; }
  .item-table input, .item-table select, .item-table textarea { font-size: 14px; padding: 4px 6px 4px 10px; }
  .item-table select {
    height: var(--control-height);
    padding: 8px 28px 8px 8px;
    border-radius: 8px;
  }
  .dev-item-table { margin-bottom: 0; }
  .dev-item-table tbody td {
    padding-top: 6px;
    padding-bottom: 6px;
    border-bottom: 0;
  }
  /* 항목형(templateType==='item') 개발비용의 상세내역은 여러 줄 textarea라 다른 셀보다
     키가 커질 수 있어서, 그 행의 셀들은 세로 중앙이 아니라 상단 정렬로 맞춤. */
  .dev-item-table tbody tr.item-row-top td { vertical-align: top; padding-top: 10px; }
  /* tbody의 실제 마지막 행("+ 항목 추가" 줄)도 위/아래 보더 둘 다 유지하되, 위와 같은 이유로
     border 대신 box-shadow(inset)로 그려서 위/아래가 항상 같은 두께/색으로 보이게 함. */
  .dev-item-table tbody tr:last-child td,
  .item-table:not(.dev-item-table) tbody tr:last-child td {
    border-top: none;
    border-bottom: none;
    box-shadow: inset 0 1px 0 0 var(--border), inset 0 -1px 0 0 var(--border);
  }
  /* 항목이 0개면 "+ 항목 추가" 줄이 헤더 바로 아래(=tbody의 유일한 행)에 오는데,
     그러면 위 규칙의 위쪽 선(box-shadow)이 헤더 자신의 아래쪽 선과 같은 자리에서
     겹쳐 보이므로, 이 경우엔 위쪽 선만 생략하고 아래쪽 선만 남김.
     (바로 위 규칙 중 .item-table:not(.dev-item-table) 쪽과 특이도가 같아서 밀릴 수 있어
     .item-add-cell 클래스까지 셀렉터에 포함해 확실히 더 높은 특이도로 만듦) */
  .item-table tbody tr.item-add-row.item-add-row-empty td.item-add-cell {
    box-shadow: inset 0 -1px 0 0 var(--border);
  }

  /* 개발비용/기타 부대비용 표 안의 한 줄짜리 폼(텍스트/숫자 입력, 드롭다운)만 뗀 클래스.
     상세내역처럼 여러 줄인 textarea는 대상에서 제외(기존 스타일 그대로).
     박스 높이(field-input-cell)와 텍스트 스타일(field-input-cell-text)을 분리해뒀으니
     폰트 사이즈 등은 .field-input-cell-text 쪽만 건드리면 됨. */
  .item-table .field-input-cell { height: 32px; }
  .item-table .field-input-cell-text { font-size: 13px; }
  /* 단가 = 우측정렬, MM(개발비용)/수량(기타 부대비용) = 중앙정렬, 나머지는 기본(좌측정렬) */
  /* 단가/금액(품목형 편집 가능 금액 포함, 둘 다 -right 클래스 공유)만 패딩을 좌우 10px 균등으로 —
     MM/수량(-center)은 건드리지 않아 위 .item-table input 기본값(4px 6px 4px 10px, 사용자 직접 커스터마이즈)
     그대로 유지됨. 특이도(class 2개)가 기본 규칙(.item-table input, class+태그)보다 높아 확실히 덮어씀. */
  .item-table .field-input-cell-right { text-align: right; padding: 4px 10px; }
  .item-table .field-input-cell-center { text-align: center; }

  /* 첫 줄/마지막 줄에만 위아래 여백을 더 줌(중간 줄은 기존 그대로) */
  .dev-item-table tbody tr.item-row-first td { padding-top: 12px; }
  .dev-item-table tbody tr.item-row-last td { padding-bottom: 12px; }
  .item-table:not(.dev-item-table) tbody tr.item-row-first td { padding-top: 14px; }
  .item-table:not(.dev-item-table) tbody tr.item-row-last td { padding-bottom: 14px; }
  .item-add-row { cursor: pointer; }
  .item-add-row .item-add-cell {
    height: calc(var(--control-height) + 20px);
    text-align: center;
    color: var(--accent);
    font-weight: 600;
    font-size: 13px;
    transition: background 0.1s ease;
  }
  .item-add-row:hover .item-add-cell { background: rgba(var(--accent-rgb),0.06); }
  .part-select-wrap { display: flex; flex-direction: column; gap: 6px; }
  .part-select-wrap select, .part-select-wrap input, .part-select-wrap .custom-select-trigger { width: 100%; }
  .amount-cell { text-align: right; vertical-align: middle !important; font-variant-numeric: tabular-nums; white-space: nowrap; padding-right: 10px !important; font-weight: 600; }
  .amount-display-input {
    text-align: right;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }
  /* 계산된 금액(비활성 표시) 필드도 단가와 동일하게 좌우 10px 균등 패딩 —
     .item-table 접두사로 특이도를 올려 위 .item-table input 기본값을 확실히 덮어씀. */
  .item-table .amount-display-input { padding: 4px 10px; }
  .icon-delete {
    width: 32px;
    height: 32px;
    min-height: 32px;
    min-width: 32px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .icon-delete svg { width: 16px; height: 16px; }

  .totals-layout {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 32px;
    align-items: start;
  }
  .totals-controls { display: flex; flex-direction: column; }
  .totals-controls .field:last-child { margin-bottom: 0; }
  /* 예전엔 .totals-box를 flex+justify-content:flex-end로 만들고 그 안의 .totals-inner에
     고정 px 너비(340px)를 줘서 "우측 끝에 어느 정도 크기의 박스를 붙이는" 방식이었음 —
     .totals-layout이 이미 1fr 1fr 2열 그리드라 우측 칸 자체는 항상 정확한 폭을 갖고
     있는데, 그 안에서 다시 숫자로 박스 폭을 지정하다 보니 실제 칸 폭과 안 맞아 여백이
     남거나(그래서 "너비를 조정하면 조정은 되는" 것처럼 보였음) 화면 크기에 따라 다시
     안 맞을 수 있는 구조였음. 우측 칸(.totals-result)이 이미 그리드로 폭을 갖고 있으니
     그 폭을 그대로 100%로 채우도록 변경 — 숫자 튜닝 없이 항상 정확히 칸에 꽉 참. */
  .totals-box {
    width: 100%;
  }
  .totals-box .totals-inner {
    width: 100%;
  }
  .totals-row {
    display: flex; justify-content: space-between; padding: 12px;
    border-bottom: 1px solid var(--border); font-size: 14px;
    font-weight: 500;
  }
  .totals-row.grand {
    font-weight: 700; font-size: 16px; border-bottom: none;
    background: var(--accent); color: #fff; padding: 16px 12px;
    /* margin-top: 6px; */
  }
  .badge {
    display: inline-block; font-size: 11px; font-weight: 600;
    padding: 2px 8px; border-radius: 100px; background: #eef0f3; color: var(--text-sub);
  }
  /* 유형(정밀형/심플형/항목형/현대오토에버/삼성 & SK)별 배지 색 구분 */
  .badge-precise { background: rgba(var(--accent-rgb),0.12); color: #3157cc; }
  .badge-simple { background: rgba(20,223,148,0.16); color: #0f9d68; }
  .badge-item { background: rgba(255,159,10,0.16); color: #b56100; }
  /* [2026-08] 현대오토에버 = 남색 */
  .badge-autoever { background: rgba(30,58,95,0.14); color: #1e3a5f; }
  /* [2026-08] 삼성 & SK = 주황 (item의 앰버톤과 구분되도록 조금 더 채도 높은 주황) */
  .badge-samsung_sk { background: rgba(255,111,0,0.18); color: #c94f00; }
  .empty-state { text-align: center; padding: 60px 20px; color: var(--text-sub); }

  /* 필터 카드 (리스트 카드 위, 별도 고정 카드) — 총 3줄:
     1줄) 검색(3칸)+고객사(1칸)  2줄) 작성일 기간(2칸)+총견적금액 구간(2칸)  3줄) 유형 체크박스 + 초기화 */
  #filter-wrap { padding: 20px 32px 24px; }
  /* "필터" 카드 레이블(헤더)은 현재 화면에서 뺐음 — 복구 시 아래 .filter-card-header 관련
     스타일은 그대로 쓸 수 있어서 지우지 않고 남겨둠(app.html의 renderFilterBar()도 참고) */
  .filter-card-header {
    display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px;
  }
  .filter-card-header h2 { font-size: 15px; display: inline-flex; align-items: center; }
  .filter-active-dot {
    display: inline-block; width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent); margin-left: 6px;
  }
  .filter-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 24px;
  }
  .filter-grid + .filter-grid { margin-top: 12px; }
  .filter-grid .field { margin-bottom: 0; }
  .filter-span-1 { grid-column: span 2; }
  .filter-span-2 { grid-column: span 2; }
  .filter-span-3 { grid-column: span 2; }
  /* 1줄 검색창 — 텍스트 앞에 아이콘(공유해주신 Assets/search.svg) 표시 */
  .field-search { position: relative; }
  .field-search input[type=text] { padding-left: 30px; }
  .field-search-icon {
    position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
    width: 16px; height: 16px; color: rgba(0,0,0,0.35); pointer-events: none;
  }
  .field-search-icon svg { width: 16px; height: 16px; display: block; }

  /* 2줄(기간/금액구간) 내부의 "시작 ~ 종료" 두 입력을 하나의 필드처럼 묶어서 표시 */
  .filter-range { display: flex; align-items: center; gap: 8px; }
  .filter-range input { min-width: 0; flex: 1 1 0; }
  .filter-range-sep { flex: 0 0 auto; color: var(--text-sub); font-size: 12px; }
  /* 기간 필드는 이제 견적서작성폼과 동일한 달력 트리거 버튼(.period-date-trigger)을
     쓰므로, 그 트리거를 감싸는 래퍼(.filter-date-field)만 필터 전용으로 추가 —
     팝업 위치 기준점(position:relative)과 flex 배분을 위한 최소 스타일만 필요. */
  .filter-date-field { position: relative; flex: 1 1 0; min-width: 0; }

  /* 3줄: 유형 체크박스(좌) + 초기화 버튼(우측 끝) */
  .filter-row-3 { display: flex; align-items: center; justify-content: space-between; margin-top: 20px; }
  .field-checkbox-group { display: flex; align-items: center; gap: 18px; height: var(--control-height); }
  .field-checkbox {
    position: relative; display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-weight: 400;
  }
  /* 네이티브 체크박스는 시각적으로는 숨기되(1px+opacity 0) DOM/접근성 상 그대로 남겨두고,
     클릭은 감싸는 <label>이 대신 전달받아 처리 — 지난번 공유해주신 check-on/off.svg를
     그 위에 겹쳐 보여주는 방식(pointer-events:none이라 label이 클릭을 가로챔). */
  .field-checkbox input[type=checkbox] {
    position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; pointer-events: none;
  }
  .field-checkbox-icon { display: inline-flex; flex: 0 0 auto; width: 20px; height: 20px; }
  .field-checkbox-icon svg { width: 20px; height: 20px; display: block; }
  .filter-reset-btn { flex: 0 0 auto; min-width: 0; min-height: 0; padding: 6px 12px; font-size: 14px; font-weight: 700; }
  /* 필터가 하나라도 걸려있어서 버튼이 활성화(disabled 아님) 상태일 때만 프라이머리 컬러로 —
     비활성화 상태는 기존 button:disabled의 회색(opacity:0.3) 처리를 그대로 물려받음. */
  .filter-reset-btn:not(:disabled) { color: var(--accent); }

  .list-toolbar {
    display: flex;
    align-items: end;
    justify-content: space-between;
    margin-bottom: 12px;
  }
  .list-toolbar .list-count { font-size: 13px; color: var(--text-sub); }
  .list-toolbar select {
    width: auto;
    height: 32px;
    padding: 0 28px 0 10px;
    font-size: 13px;
  }
  .pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    margin-top: 20px;
  }
  .pagination .page-nav,
  .pagination .page-num {
    min-width: 28px;
    min-height: 28px;
    height: 28px;
    padding: 0 6px;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    font-size: 13px;
    color: var(--text-sub);
    cursor: pointer;
  }
  .pagination .page-nav { display: inline-flex; align-items: center; justify-content: center; }
  .pagination .page-nav svg { width: 16px; height: 16px; flex: 0 0 auto; }
  .pagination .page-nav:hover:not(:disabled),
  .pagination .page-num:hover:not(.active) { background: var(--surface-subtle); }
  .pagination .page-nav:disabled { opacity: 0.35; cursor: not-allowed; }
  .pagination .page-num.active {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
    font-weight: 600;
  }
  .pagination .page-ellipsis {
    min-width: 20px;
    text-align: center;
    color: var(--text-muted);
    font-size: 13px;
  }
  .toast {
    position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
    background: #1a1a1a; color: #fff; padding: 10px 18px; border-radius: 8px;
    font-size: 13px; opacity: 0; pointer-events: none; transition: opacity 0.2s;
    z-index: 999;
  }
  .toast.show { opacity: 1; }
  /* [2026-08] '현대오토에버'/'삼성 & SK' 2종 추가로 3개(한 줄) → 5개(두 줄, 6번째 칸은
     향후 확장을 위해 빈칸)로 늘어나서 flex 한 줄 배치 대신 3열 그리드로 변경.
     .field-choice-empty는 내용이 없는 빈 칸이라 별도 스타일 없이도 자리만 차지하고
     투명하게 남는다(테두리 박스 없음 — "그냥 비워놓기"). */
  .field-choice-group { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 4px; }
  .field-choice-group label {
    border: 1px solid rgba(0,0,0,0.2); border-radius: 8px; padding: 16px 12px;
    font-weight: 500; font-size: 14px; line-height: 20px; color: var(--text); cursor: pointer;
    display: flex; align-items: center; gap: 6px;
    transition: background 0.1s ease, border-color 0.1s ease, box-shadow 0.1s ease;
  }
  .field-choice-group label:hover { border-color: var(--accent); box-shadow: inset 0 0 0 0.5px var(--accent); }
  .field-choice-group label.active { border-color: var(--accent); box-shadow: inset 0 0 0 0.5px var(--accent); background: var(--accent-soft); }
  .field-choice-icon { position: relative; display: inline-flex; width: 20px; height: 20px; flex: 0 0 auto; }
  .field-choice-icon svg { position: absolute; inset: 0; width: 100%; height: 100%; }
  .field-choice-ring { color: rgba(0,0,0,0.3); }
  .field-choice-dot { color: var(--accent); opacity: 0; transition: opacity 0.15s ease; }
  .field-choice-group input[type="radio"] {
    position: absolute;
    opacity: 0;
    width: 20px;
    height: 20px;
    margin: 0;
    cursor: pointer;
  }
  .field-choice-group input[type="radio"]:checked ~ .field-choice-icon .field-choice-dot { opacity: 1; }

  /* dimmed 배경 전담(반투명 rgba, 스크롤 없음)과 스크롤 전담(투명, .form-modal-scroll)을
     분리한 이유: 사파리에서 "position:fixed + overflow-y:auto + 반투명 배경"을 한 요소에
     같이 두면, 내용이 뷰포트보다 길어져 실제 스크롤이 생기는 시점부터 그 반투명 배경이
     타일링되며 알파값이 겹쳐 보이는(의도보다 훨씬 어둡게 렌더링되는) 버그가 있었음
     (로딩 중 짧은 내용일 땐 정상, 폼이 다 그려져 스크롤이 생기는 순간부터 어두워짐 —
     사용자가 직접 관찰해서 알려준 재현 조건. 크롬은 이 요소 구조와 무관하게 문제없었음).
     이제 배경(.form-modal-overlay)은 스크롤을 갖지 않는 순수 오버레이라 타일링 자체가
     안 생기고, 스크롤은 배경이 없는 .form-modal-scroll이 전담. */
  .form-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 150;
    background: rgba(15,17,21,0.42);
    opacity: 0;
    transition: opacity 0.2s ease;
  }
  .form-modal-overlay.hidden { display: none; }
  .form-modal-overlay.visible { opacity: 1; }
  .form-modal-scroll {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    overflow-y: auto;
    padding: 40px 20px;
  }
  .form-modal-panel {
    position: relative;
    width: min(1024px, 100%);
    background: var(--modal-bg);
    border-radius: 28px;
    box-shadow: 0 24px 64px rgba(0,0,0,0.28);
    padding: 40px 48px 24px;
    transform: translateY(12px);
    transition: transform 0.2s ease;
    /* .navbar와 같은 이유(will-change 미리 레이어 승격) — 이 패널 자체엔 backdrop-filter가
       없지만, opacity가 전환 중인 부모(.form-modal-overlay) 바로 아래에서 매 프레임 다시
       페인트되는 걸 줄여서 열리는 순간의 깜빡임을 완화함. */
    will-change: transform;
  }
  .form-modal-overlay.visible .form-modal-panel { transform: translateY(0); }
  .form-modal-close {
    position: absolute;
    top: 16px;
    right: 16px;
    width: 36px;
    height: 36px;
    min-width: 36px;
    border-radius: 50%;
    border: none;
    background: rgba(0,0,0,0.05);
    color: var(--text-sub);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .form-modal-close svg { width: 18px; height: 18px; flex: 0 0 auto; }
  .form-modal-close:hover { background: rgba(0,0,0,0.1); color: var(--text); }

  /* 기술등급 변경 확인 팝업. formModalOverlay(z-index:150) 위에 겹쳐 떠야 해서 더 높은 z-index 사용. */
  .grade-confirm-overlay {
    position: fixed;
    inset: 0;
    z-index: 250;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15,17,21,0.42);
  }
  .grade-confirm-overlay.hidden { display: none; }
  .grade-confirm-panel {
    width: min(360px, calc(100% - 40px));
    background: var(--modal-bg);
    border-radius: 20px;
    box-shadow: 0 24px 64px rgba(0,0,0,0.28);
    padding: 16px;
  }
  .grade-confirm-message {
    font-size: 15px;
    line-height: 24px;
    color: var(--text);
    white-space: pre-line;
    margin: 8px 8px 20px;
  }
  .grade-confirm-checkbox {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--text-sub);
    cursor: pointer;
    margin-bottom: 20px;
  }
  .grade-confirm-checkbox input[type="checkbox"] { width: 16px; height: 16px; margin: 0; cursor: pointer; }
  .grade-confirm-actions { display: flex; justify-content: flex-end; gap: 8px; }
  /* [2026-08] 저장 방식 선택 팝업만 버튼이 3개(취소/덮어쓰기/새 버전으로 저장)라
     기본 360px 패널에 기본 버튼(min-width 100px)들을 다 놓으면 자리가 부족함 —
     이 팝업에서만 패널 폭을 넓힘. */
  #saveModeConfirmOverlay .grade-confirm-panel { width: min(440px, calc(100% - 40px)); }

  @media (max-width: 800px) {
    .form-modal-panel { padding: 40px 32px 20px; }
    .form-modal-scroll { padding: 0; }
    .logo { left: 20px; }
    .nav-right { right: 20px; }
    #app { margin-top: 24px; padding: 0 16px; }
    .card { padding: 22px 20px; }
    .grid-2, .grid-3, .content-card-grid, .rate-settings-grid, .totals-layout { grid-template-columns: 1fr; }
    .basic-info-grid { grid-template-columns: 1fr; }
    .field-span-2, .field-span-1 { grid-column: span 1; }
    .period-calendar-popover { width: min(232px, calc(100vw - 72px)); }
    .field-choice-group { grid-template-columns: 1fr; }
  }

  /* ------------------------------------------------------------------
     [2026-08] 구글 로그인 화면 — Resource Manager의 #loginScreen과 동일한
     구성(전체화면 오버레이 + 가운데 카드 + 구글 로그인 버튼)을 이 앱의
     기존 카드 스타일(--surface/--shadow-card/--border)에 맞춰 재구성.
     로그인 전에는 이 오버레이가 nav+#app 전체를 가리고, 로그인 성공 시
     .hidden 클래스만 토글해서 사라진다(별도 페이지 이동 없음).
     ------------------------------------------------------------------ */
  #loginScreen {
    position: fixed;
    inset: 0;
    z-index: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--bg);
  }
  #loginScreen.hidden { display: none; }
  .login-card {
    width: 360px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: var(--shadow-card);
    padding: 40px 32px;
    text-align: center;
  }
  .login-card .login-logo { margin: 0 auto 24px; display: flex; justify-content: center; }
  .login-card h1 {
    font-size: 18px;
    font-weight: 700;
    margin: 0 0 8px;
    color: var(--text);
  }
  .login-card p {
    font-size: 13px;
    color: var(--text-sub);
    margin: 0 0 24px;
    line-height: 1.6;
  }
  #googleSignInBtn { display: flex; justify-content: center; }
  .login-error {
    margin-top: 16px;
    font-size: 12px;
    color: var(--danger);
    display: none;
  }
  .login-error.show { display: block; }
