Files
Aislo/B03_FileInput/B03_FileInput_UI_Style.css
T
eomsangdonandClaude Opus 5 1eb5ad4a60 feat(B03): 입력 화면을 계획노선/지형 두 컨테이너로 나누고 파일마다 카드를 준다
노선 파일이 다섯이면 카드도 다섯이어야 어느 것이 왔는지 보인다(사용자 지시).
세트를 슬롯 하나에 몰아 담던 companions 구조를 걷어내고, 슬롯 하나가 파일 하나를
갖는 기존 구조로 되돌렸다 - 업로드 루프도 원래대로다.

- 왼쪽 컨테이너 계획노선 자료: csv(.csv/.shp) shx dbf cpg route_prj
- 오른쪽 컨테이너 지형 자료(LAS): las_laz prj(지형 좌표계) tfw tif + LAS 없는 설계 토글
- .prj만 확장자로 안 갈린다 - 노선 도형과 basename이 같으면 노선 좌표계 카드,
  아니면 지형 카드(planSlotAssignments). 재접속 현황은 저장 경로(input/shp/)로 가른다.
- 재접속 현황 응답에 relative_path 추가.
- 필수 판정이 노선 PRJ를 route_prj로 따로 센다 - 지형 PRJ 없이 통과하던 구멍을 막았다.
- 형제 카드(shx/dbf/route_prj)는 노선 도형이 shapefile일 때만 필수 - 확장자 줄의
  "선택" 꼬리표가 실시간으로 붙고 떨어진다.
- 카드가 좁아져 한글 제목이 글자 단위로 접히던 것을 word-break: keep-all과 헤더
  flex-wrap으로 고쳤다.

화면 검증(공용 브라우저): 실물 7파일을 한 번에 떨어뜨려 배정 실측 - route.prj는
노선 좌표계 카드, terrain.prj는 지형 카드로 갈렸고 카드 제목 9개 모두 한 줄.
tmp/tests/test_route_shapefile_input.py 9개 통과, tsc --noEmit 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:50:39 +09:00

524 lines
13 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.b03-file {
min-height: calc(100vh - var(--app-header-height, 0px));
background: linear-gradient(
180deg,
var(--color-canvas, #ffffff) 0%,
var(--color-mist-violet, #edecff) 100%
);
padding: 0 var(--spacing-24) var(--spacing-48) var(--spacing-24); /* 상단 여백은 main-layout 마진으로 조정하므로 padding-top은 0 */
}
.b03-file__main-layout {
max-width: var(--page-max-width, 1200px);
margin: var(--spacing-40) auto 0 auto; /* 상단 구분선(헤더)과의 간격을 확실하게 40px 벌림 */
display: flex;
flex-direction: column;
gap: var(--spacing-32);
}
.b03-file__control-panel {
display: flex;
flex-direction: column;
gap: var(--spacing-20);
background: var(--color-canvas, #ffffff);
padding: var(--spacing-32);
border-radius: var(--radius-large, 24px); /* Wiza 24px 대형 둥글기 */
border: 1px solid var(--color-mist, #e6e2e3);
box-shadow: var(--shadow-lg);
}
.b03-file__cards-container-panel {
margin-top: 0; /* 단일 layout gap에 의해 제어되도록 설정 */
}
.b03-file__subtitle {
font-size: var(--text-body-sm, 14px);
color: var(--color-slate, #615e6e);
margin: 0 0 var(--spacing-8) 0;
}
.b03-file__native-input,
.b03-file__slot-input {
display: none;
}
/* Wiza 스타일 가이드의 Lavender Glow 그라데이션 및 soft purple border hover */
.b03-file__dropzone {
min-height: 160px;
padding: var(--spacing-32) var(--spacing-24);
border: 2px dashed var(--color-mist, #e6e2e3);
border-radius: var(--radius-large, 24px);
background: var(--color-paper, #f6f7fa);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--spacing-8);
cursor: pointer;
text-align: center;
transition:
background var(--transition-base, 0.2s),
border-color var(--transition-base, 0.2s),
box-shadow var(--transition-base, 0.2s);
}
.b03-file__dropzone:hover {
background: var(--color-mist-violet, #edecff);
border-color: var(--color-royal-amethyst, #3e0079);
}
.b03-file__dropzone:focus-visible,
.b03-file__dropzone.is-dragging {
outline: none;
background: var(--color-mist-violet, #edecff);
border-color: var(--color-royal-amethyst, #3e0079);
box-shadow: 0 0 0 4px rgba(62, 0, 121, 0.15);
}
.b03-file__dropzone strong {
color: var(--color-deep-iris, #26114a);
font-size: var(--text-body, 16px);
font-weight: var(--font-weight-medium, 500);
}
.b03-file__dropzone span {
font-size: var(--text-body-sm, 14px);
color: var(--color-slate, #615e6e);
}
.b03-file__error {
min-height: var(--spacing-16);
color: var(--color-danger, #dc2626);
font-size: var(--text-body-sm, 14px);
margin: var(--spacing-8) 0;
}
.b03-file__resume {
display: none;
align-items: center;
gap: var(--spacing-8);
flex-wrap: wrap;
padding: var(--spacing-12) var(--spacing-16);
border: 1px solid var(--color-mist, #e6e2e3);
border-radius: var(--radius-cards, 8px);
background: var(--color-paper, #f6f7fa);
color: var(--color-deep-iris, #26114a);
font-size: var(--text-body-sm, 14px);
margin-top: var(--spacing-8);
}
.b03-file__resume.is-visible {
display: flex;
}
/* 재접속 현황(서버 정본) 배너 — 완료 안내와 중단 세션 이어올리기 안내를 담는다. */
.b03-file__overview {
display: none;
flex-direction: column;
gap: var(--spacing-4);
margin-top: var(--spacing-8);
padding: var(--spacing-12) var(--spacing-16);
border: 1px solid var(--color-border);
border-radius: var(--radius-cards, 8px);
background: var(--color-surface);
font-size: var(--text-body-sm, 14px);
}
.b03-file__overview.is-visible {
display: flex;
}
.b03-file__overview-complete {
margin: 0;
color: var(--color-success, #3fbb6f);
font-weight: 600;
}
.b03-file__overview-pending {
margin: 0;
color: var(--color-text-secondary);
}
/* 완료 슬롯 재업로드 확인 모달 — 페이지 위를 덮는 단순 확인창. */
.b03-file__modal-backdrop {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 45%);
}
.b03-file__modal {
display: flex;
flex-direction: column;
gap: var(--spacing-12);
max-width: 420px;
padding: var(--spacing-24);
border: 1px solid var(--color-border);
border-radius: var(--radius-cards, 8px);
background: var(--color-surface-raised);
box-shadow: 0 12px 32px rgb(0 0 0 / 30%);
}
.b03-file__modal p {
margin: 0;
color: var(--color-text-secondary);
white-space: pre-line;
}
.b03-file__modal-actions {
display: flex;
gap: var(--spacing-8);
justify-content: flex-end;
}
.b03-file__cards-grid {
display: grid;
grid-template-columns: 1fr;
gap: var(--spacing-32);
}
/* 계획노선 | 지형(LAS) — 컨테이너를 둘로 나눈다(2026-08-31 사용자 지시).
노선은 카드 5장(shapefile 한 벌), 지형은 4장이라 폭을 5:4 비슷하게 준다. */
.b03-file__columns {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--spacing-24);
align-items: start;
}
.b03-file__group {
display: flex;
flex-direction: column;
gap: var(--spacing-20);
}
.b03-file__group-hint {
font-size: var(--text-body-sm, 14px);
color: var(--color-slate, #615e6e);
margin: calc(var(--spacing-8) * -1) 0 0 0;
}
.b03-file__group-title {
font-size: var(--text-subheading, 24px);
color: var(--color-deep-iris, #26114a);
font-family: var(--font-britti-sans, inherit);
margin: 0 0 var(--spacing-8) 0;
}
.b03-file__group-content {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--spacing-24);
}
/* 업로드 중에는 화면 조작을 막는다 — 중복 업로드·단계 이동 방지(2026-08-08 사용자 지시). */
.b03-file--busy {
pointer-events: none;
}
.b03-file--busy .b03-file__card-select,
.b03-file--busy .b03-file__card-remove,
.b03-file--busy .b03-file__dropzone {
opacity: 0.5;
}
/* 업로드 버튼 안에서 도는 원 — 공용 스피너를 버튼 글자 크기에 맞춰 줄인다. */
.b03-file__upload-spinner {
width: 14px;
height: 14px;
border-width: 2px;
border-color: rgb(255 255 255 / 40%);
border-top-color: var(--color-primary-text);
}
/* Wiza 8px radius 카드 */
.b03-file__card {
min-height: 220px;
padding: var(--spacing-24); /* 내부 전체 패딩 확대 */
border: 1px solid var(--color-mist, #e6e2e3);
border-radius: var(--radius-cards, 8px);
background: var(--color-canvas, #ffffff);
box-shadow: var(--shadow-sm);
display: flex;
flex-direction: column;
gap: var(--spacing-20);
transition:
transform var(--transition-base, 0.2s),
border-color var(--transition-base, 0.2s),
box-shadow var(--transition-base, 0.2s);
}
.b03-file__card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
border-color: var(--color-royal-amethyst, #3e0079);
}
.b03-file__card--selected {
border-color: var(--color-royal-amethyst, #3e0079);
background: var(--color-paper, #f6f7fa);
}
.b03-file__card--uploading {
border-color: var(--color-royal-amethyst, #3e0079);
box-shadow: var(--shadow-lg-2);
}
.b03-file__card--completed {
border-color: #10b981; /* Success Green */
}
.b03-file__card--error {
border-color: var(--color-danger, #dc2626);
}
/* 카드가 좁아졌다(컨테이너 2열 × 카드 2열, 2026-08-31). 격자로 고정하면 제목이
글자 단위로 접히므로, 자리가 모자라면 배지·삭제 버튼이 다음 줄로 내려가게 한다. */
.b03-file__card-header {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-8);
align-items: center;
}
.b03-file__card-icon {
flex: 0 0 auto;
width: 28px;
height: 28px;
border-radius: var(--radius-icons, 8px);
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--color-royal-amethyst, #3e0079);
background: var(--color-mist-violet, #edecff);
font-size: var(--text-body-sm, 14px);
margin-right: var(
--spacing-8
); /* 아이콘 우측 마진 추가 (아이콘 좌측 여유 확대 효과) */
}
.b03-file__card-heading {
min-width: 0;
flex: 1 1 55%;
display: flex;
flex-direction: column;
gap: 2px;
margin-top: 2px; /* 제목 상단 여유 추가 */
}
.b03-file__card-label {
color: var(--color-deep-iris, #26114a);
font-size: var(--text-body-sm, 14px);
font-weight: var(--font-weight-medium, 500);
/* 한글은 글자 단위로 끊기므로 어절을 지켜 준다 — "계획
노선
도형" 방지. */
word-break: keep-all;
overflow-wrap: break-word;
}
.b03-file__card-ext,
.b03-file__file-size,
.b03-file__progress-info {
color: var(--color-slate, #615e6e);
font-size: var(--text-caption, 12px);
}
.b03-file__card-badge-container {
display: flex;
align-items: center;
margin-left: auto;
margin-right: var(--spacing-4);
}
.b03-file__card-remove {
width: 24px;
height: 24px;
border: 1px solid var(--color-mist, #e6e2e3);
border-radius: var(--radius-buttons, 8px);
color: var(--color-slate, #615e6e);
background: var(--color-canvas, #ffffff);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--text-body-sm, 14px);
line-height: 1;
padding: 0;
margin-left: var(
--spacing-8
); /* 취소 버튼 좌측 여유 추가 (취소 버튼 우측 여유 확보) */
transition: all var(--transition-base, 0.2s);
}
.b03-file__card-remove:hover {
color: var(--color-danger, #dc2626);
border-color: var(--color-danger, #dc2626);
background: #fef2f2;
}
.b03-file__card-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: var(--spacing-16); /* 버튼 하단 및 내부 간격 확대 */
padding-bottom: var(--spacing-8); /* 파일 선택 버튼 하단 패딩 확보 */
}
.b03-file__card-select {
width: 100%;
min-height: 40px; /* 버튼 높이 증가 */
border: 1px solid var(--color-mist, #e6e2e3);
border-radius: var(--radius-buttons, 8px);
color: var(--color-deep-iris, #26114a);
background: var(--color-canvas, #ffffff);
font-weight: var(--font-weight-medium, 500);
font-size: var(--text-body-sm, 14px);
cursor: pointer;
margin-bottom: var(--spacing-8); /* 파일 선택 버튼 하단 마진 추가 */
transition:
background var(--transition-base, 0.2s),
border-color var(--transition-base, 0.2s);
}
.b03-file__file-info {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.b03-file__file-name {
color: var(--color-deep-iris, #26114a);
font-size: var(--text-body-sm, 14px);
font-weight: var(--font-weight-medium, 500);
overflow-wrap: anywhere;
}
.b03-file__progress-section {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
}
.b03-file__progress-bar-container {
width: 100%;
height: 6px;
overflow: hidden;
border-radius: var(--radius-pills, 1440px);
background: var(--color-mist, #e6e2e3);
}
.b03-file__progress-bar {
width: 0;
height: 100%;
border-radius: var(--radius-pills, 1440px);
background: var(--color-royal-amethyst, #3e0079);
transition: width var(--transition-base, 0.2s);
}
.b03-file__progress-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.b03-file__error-message {
display: none;
padding: var(--spacing-8) var(--spacing-12);
border-radius: var(--radius-cards, 8px);
color: var(--color-danger, #dc2626);
background: #fef2f2;
font-size: var(--text-caption, 12px);
border: 1px solid rgba(220, 38, 38, 0.15);
}
.b03-file__card--empty .b03-file__file-info,
.b03-file__card--empty .b03-file__progress-section,
.b03-file__card--empty .b03-file__error-message {
display: none;
}
.b03-file__card--selected .b03-file__progress-section {
display: none;
}
.b03-file__card--uploading .b03-file__card-select,
.b03-file__card--completed .b03-file__card-select {
display: none;
}
.b03-file__card--error .b03-file__error-message {
display: block;
}
.b03-file__card--error .b03-file__progress-section {
display: none;
}
.b03-file__results {
margin: var(--spacing-8) 0 0 0;
padding: 0;
list-style: none;
border: 1px solid var(--color-mist, #e6e2e3);
border-radius: var(--radius-cards, 8px);
overflow: hidden;
background: var(--color-canvas, #ffffff);
}
.b03-file__results:empty {
display: none;
}
.b03-file__results li {
padding: var(--spacing-12) var(--spacing-16);
display: flex;
flex-direction: column;
gap: 2px;
border-bottom: 1px solid var(--color-mist, #e6e2e3);
color: var(--color-deep-iris, #26114a);
font-size: var(--text-body-sm, 14px);
}
.b03-file__results li:last-child {
border-bottom: 0;
}
@media (max-width: 1440px) {
/* 좁아지면 두 컨테이너를 위아래로 쌓는다 — 카드가 눌려 글자가 접히는 것을 막는다. */
.b03-file__columns {
grid-template-columns: 1fr;
}
}
@media (max-width: 720px) {
.b03-file {
padding: var(--spacing-16) var(--spacing-12);
}
.b03-file__group-content {
grid-template-columns: 1fr; /* 모바일에서는 1행 1열 구조 */
}
}
/* LAS 없는 설계(도엽등고선 기반) 토글 — 2026-08-30 */
.b03-file__lasfree {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 2px;
font-size: var(--text-body);
color: var(--color-text-body);
cursor: pointer;
user-select: none;
}
.b03-file__card--disabled {
opacity: 0.45;
pointer-events: none;
}