Files
invyone/frontend/styles/developer.css
T
gbpark 2c0a97f2ba Phase 1: INVYONE 카드 엔진 토대 정리
- components/builder/* 폐기 (12-grid 미완성 빌더 14개 파일)
- components/template-builder/TemplateBuilder.tsx 신규
  (자유배치 + 3뷰 + Zustand 스토어 + 드래그/리사이즈/히스토리/격자)
- admin/builder/page.tsx 진입점 전환 (BuilderLayout → TemplateBuilder)
- 타입 정리: FreePosition / TemplateComponent / ViewConfig / Card /
  Dashboard / CardConnection 추가, 레거시(GridPosition/TemplateKind/
  DEFAULT_COMPONENT_LAYOUTS/CANVAS_KEYWORDS) @deprecated 표기
- v2-* 마이그레이션 1차:
  · 완전: v2-table-list (ResizeObserver), v2-table-search-widget (@container)
  · 경량: button/input/select/date/text-display/card-display/aggregation-widget
    (withContainerQuery HOC)
- 다크 모드 대응: Tailwind dark: variant 21패턴 71곳 치환
- /test-card-responsive PoC 검증 페이지

세션 후반 버그 픽스 (phase1-log §7):
- test-card-responsive (main) 그룹 밖 이동 (AppLayout 탭 시스템 회피)
- useRegistryPalette default_size {width,height}/{w,h} 포맷 정규화
- dark: variant 중복 체인 정리

검증: (A) 반응형 메커니즘, (B) TemplateBuilder UI 통과
(C) 기존 VEX 화면은 마이그레이션 미완 상태라 Phase 2 이후 개별 진행

스펙: notes/gbpark/2026-04-10-card-engine-final-spec.md
로그: notes/gbpark/2026-04-10-card-engine-phase1-log.md

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 03:08:06 +09:00

442 lines
16 KiB
CSS

/* ═══════════════════════════════════════════════════════════════════════════
★ 개발자 빌더 — IDE 스타일 프로페셔널 테마
코스믹 글래스모피즘 X → 깔끔한 IDE/Figma 스타일
mockup 09-developer.css 기반 React 포팅
═══════════════════════════════════════════════════════════════════════════ */
/* ─── 개발자 전용 색상 (다크) ─── */
.dark .dev-shell {
--d-bg: #121218;
--d-bg2: #1a1a22;
--d-bg3: #22222c;
--d-surface: #2a2a36;
--d-surface2: #32323f;
--d-border: #3a3a48;
--d-border2: #4a4a58;
--d-text: #e8e8ee;
--d-text2: #b0b0be;
--d-text3: #78788a;
--d-accent: #5b9ef5;
--d-accent2: #4a8de6;
--d-green: #4ade80;
--d-cyan: #22d3ee;
--d-orange: #fb923c;
--d-pink: #f472b6;
--d-red: #f87171;
}
/* ─── 개발자 전용 색상 (라이트) ─── */
:root:not(.dark) .dev-shell,
html:not(.dark) .dev-shell {
--d-bg: #f5f5f8;
--d-bg2: #ededf2;
--d-bg3: #e4e4ec;
--d-surface: #fff;
--d-surface2: #f8f8fb;
--d-border: #d8d8e2;
--d-border2: #c4c4d0;
--d-text: #1a1a24;
--d-text2: #5a5a6e;
--d-text3: #8a8a9e;
--d-accent: #3b7dd8;
--d-accent2: #2d6bc4;
--d-green: #16a34a;
--d-cyan: #0891b2;
--d-orange: #ea580c;
--d-pink: #db2777;
--d-red: #dc2626;
}
/* ═══ 셸 ═══ */
.dev-shell {
display: flex;
flex-direction: column;
height: 100vh;
position: relative;
z-index: 1;
background: var(--d-bg);
color: var(--d-text);
font-family: inherit;
}
/* ─── 헤더 ─── */
.dev-hdr {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0.8rem;
height: 42px;
background: var(--d-bg2);
border-bottom: 1px solid var(--d-border);
z-index: 10;
flex-shrink: 0;
}
.dev-hdr-l { display: flex; align-items: center; gap: 0.6rem; }
.dev-logo { font-size: 0.72rem; font-weight: 800; letter-spacing: -0.02em; color: var(--d-accent); }
.dev-badge {
font-size: 0.48rem; font-weight: 700; padding: 0.12rem 0.4rem; border-radius: 4px;
background: var(--d-accent); color: #fff;
}
.dev-hdr-r { display: flex; align-items: center; gap: 0.35rem; }
/* ─── 도구모음 ─── */
.dev-toolbar {
display: flex; align-items: center; gap: 0.4rem; padding: 0 0.8rem;
background: var(--d-bg2); border-bottom: 1px solid var(--d-border); height: 34px;
flex-shrink: 0;
}
.dev-tb-group {
display: flex; align-items: center; gap: 0.25rem;
padding-right: 0.5rem; border-right: 1px solid var(--d-border); margin-right: 0.15rem;
}
.dev-tb-group:last-child { border-right: none; }
.dev-tb-label {
font-size: 0.44rem; font-weight: 700; color: var(--d-text3);
text-transform: uppercase; letter-spacing: 0.04em; margin-right: 0.15rem;
}
/* ─── 공통 버튼/셀렉트/인풋 ─── */
.dev-btn {
padding: 0.22rem 0.55rem; border-radius: 5px; border: 1px solid var(--d-border);
background: var(--d-bg3); color: var(--d-text2); font-size: 0.52rem; font-weight: 600;
cursor: pointer; transition: all 0.12s; display: flex; align-items: center; gap: 0.25rem;
}
.dev-btn:hover { border-color: var(--d-accent); color: var(--d-text); background: var(--d-surface); }
.dev-btn.primary { background: var(--d-accent); border-color: var(--d-accent); color: #fff; }
.dev-btn.primary:hover { background: var(--d-accent2); }
.dev-select {
padding: 0.12rem 0.3rem; border-radius: 4px; border: 1px solid var(--d-border);
background: var(--d-bg3); color: var(--d-text); font-size: 0.52rem; outline: none;
}
.dev-select:focus { border-color: var(--d-accent); }
.dev-input {
padding: 0.18rem 0.35rem; border-radius: 4px; border: 1px solid var(--d-border);
background: var(--d-bg3); color: var(--d-text); font-size: 0.5rem; outline: none;
width: 100%; box-sizing: border-box;
}
.dev-input:focus { border-color: var(--d-accent); }
/* ─── 뷰 탭 ─── */
.dev-view-tab {
padding: 0.12rem 0.4rem; border-radius: 4px; border: 1px solid transparent;
background: transparent; color: var(--d-text3); font-size: 0.52rem; cursor: pointer;
transition: all 0.1s; font-weight: 600;
}
.dev-view-tab:hover { background: var(--d-surface); color: var(--d-text); }
.dev-view-tab.active { background: var(--d-accent); color: #fff; border-color: var(--d-accent); }
/* ═══ 3패널 ═══ */
.dev-body { display: flex; flex: 1; overflow: hidden; }
/* ─── 좌: 팔레트 ─── */
.dev-palette {
width: 180px; min-width: 180px; border-right: 1px solid var(--d-border);
background: var(--d-bg2); overflow-y: auto; flex-shrink: 0;
}
.dev-pal-header {
padding: 0.4rem 0.55rem; font-size: 0.48rem; font-weight: 700;
color: var(--d-text3); text-transform: uppercase; letter-spacing: 0.06em;
border-bottom: 1px solid var(--d-border);
}
.dev-pal-sec {
padding: 0.4rem 0.55rem 0.15rem; font-size: 0.42rem; font-weight: 700;
color: var(--d-accent); text-transform: uppercase; letter-spacing: 0.05em;
margin-top: 0.2rem;
}
.dev-pal-item {
display: flex; align-items: center; gap: 0.4rem; padding: 0.28rem 0.55rem;
font-size: 0.56rem; font-weight: 500; color: var(--d-text2); cursor: grab;
transition: all 0.1s; border-radius: 4px; margin: 1px 3px;
}
.dev-pal-item:hover { background: var(--d-surface); color: var(--d-text); }
.dev-pal-item:active { cursor: grabbing; background: var(--d-surface2); }
.dev-pal-icon {
width: 18px; height: 18px; border-radius: 4px; display: flex;
align-items: center; justify-content: center; font-size: 0.65rem; flex-shrink: 0;
}
/* 카테고리별 아이콘 색상 */
.dev-pal-item[data-cat="data"] .dev-pal-icon { color: var(--d-accent); }
.dev-pal-item[data-cat="input"] .dev-pal-icon { color: var(--d-green); }
.dev-pal-item[data-cat="action"] .dev-pal-icon { color: var(--d-pink); }
.dev-pal-item[data-cat="display"] .dev-pal-icon { color: var(--d-orange); }
/* ─── 중: 캔버스 ─── */
.dev-canvas {
flex: 1; overflow: auto; position: relative; background: var(--d-bg);
}
.dark .dev-canvas {
background-image: radial-gradient(circle, rgba(255,255,255,0.03) 0.5px, transparent 0.5px);
background-size: 20px 20px;
}
:root:not(.dark) .dev-canvas,
html:not(.dark) .dev-canvas {
background-image: radial-gradient(circle, rgba(0,0,0,0.06) 0.5px, transparent 0.5px);
background-size: 20px 20px;
}
.dev-canvas-inner {
position: relative; min-width: 1200px; min-height: 800px; padding: 16px;
}
/* 12-col grid 캔버스 (business kind) */
.dev-canvas-grid {
position: relative;
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
gap: 8px;
padding: 16px;
min-height: 600px;
box-sizing: border-box;
container-type: inline-size;
container-name: card;
background-image:
linear-gradient(to right, var(--grid-line, rgba(108,92,231,.08)) 1px, transparent 1px);
background-size: calc((100% - 32px) / 12 + 8px) 100%;
background-position: 16px 0;
background-repeat: repeat-x;
}
.dev-canvas-grid.dragging {
background-image:
linear-gradient(to right, var(--grid-line-hover, rgba(108,92,231,.2)) 1px, transparent 1px);
}
/* 빌더 팝업 프레임 안의 grid */
.dev-popup-grid {
min-height: 320px;
padding: 12px;
}
/* grid 경고 — 비-grid 블록 잔존 시 */
.dev-grid-warn {
grid-column: 1 / -1;
padding: .5rem .75rem;
font-size: .5rem;
color: var(--d-orange, #e29a45);
background: rgba(255, 200, 60, .08);
border: 1px dashed rgba(255, 200, 60, .35);
border-radius: 6px;
}
/* 블록 — grid item */
.dev-block {
position: relative;
min-width: 0;
min-height: 48px;
border: 1.5px dashed var(--d-border2);
border-radius: 6px;
background: var(--d-bg2);
cursor: move;
transition: border-color 0.1s, box-shadow 0.1s;
overflow: hidden;
}
.dev-block:hover { border-color: var(--d-accent); box-shadow: 0 0 0 1px var(--d-accent); }
.dev-block.selected {
border-color: var(--d-accent); border-style: solid; border-width: 2px;
box-shadow: 0 0 0 3px rgba(91,158,245,0.15);
}
.dev-block-label {
position: absolute; top: -1px; left: 6px; padding: 0 0.3rem;
font-size: 0.4rem; font-weight: 700; color: var(--d-accent); background: var(--d-bg);
letter-spacing: 0.02em; z-index: 1;
}
.dev-block.selected .dev-block-label {
background: var(--d-accent); color: #fff;
border-radius: 0 0 3px 3px; padding: 0.02rem 0.3rem;
}
.dev-block-content {
padding: 0.4rem; font-size: 0.5rem; color: var(--d-text2);
pointer-events: none; height: 100%; display: flex; flex-direction: column;
padding-top: 0.7rem;
}
/* 리사이즈 핸들 */
.dev-resize-handle {
position: absolute; bottom: 0; right: 0; width: 12px; height: 12px;
cursor: nwse-resize; z-index: 2;
}
.dev-resize-handle::after {
content: ''; position: absolute; bottom: 2px; right: 2px;
width: 6px; height: 6px; border-right: 2px solid var(--d-border2);
border-bottom: 2px solid var(--d-border2);
}
.dev-block.selected .dev-resize-handle::after {
border-color: var(--d-accent);
}
/* ─── 우: 속성 패널 ─── */
.dev-props {
width: 260px; min-width: 260px; border-left: 1px solid var(--d-border);
background: var(--d-bg2); overflow-y: auto; flex-shrink: 0;
}
.dev-prop-header {
padding: 0.4rem 0.6rem; font-size: 0.55rem; font-weight: 700; color: var(--d-text);
border-bottom: 1px solid var(--d-border); display: flex; align-items: center; gap: 0.25rem;
background: var(--d-bg3);
}
.dev-prop-sec {
padding: 0.4rem 0.6rem 0.15rem; font-size: 0.42rem; font-weight: 700;
color: var(--d-accent); text-transform: uppercase; letter-spacing: 0.04em;
border-top: 1px solid var(--d-border); margin-top: 0.15rem;
}
.dev-prop-sec:first-of-type { border-top: none; margin-top: 0; }
.dev-prop-row { padding: 0.2rem 0.6rem; display: flex; flex-direction: column; gap: 0.08rem; }
.dev-prop-row.inline { flex-direction: row; align-items: center; justify-content: space-between; }
.dev-prop-label { font-size: 0.46rem; font-weight: 600; color: var(--d-text3); }
/* 위치 그리드 (col/span/row/rowSpan 4칸) */
.dev-pos-grid {
display: grid; grid-template-columns: 1fr 1fr; gap: 0.2rem; padding: 0.2rem 0.6rem;
}
/* 반응형 오버라이드 섹션 */
.dev-resp-row {
border-top: 1px dashed var(--d-border);
padding: 0.25rem 0 0.2rem;
}
.dev-resp-row:first-of-type { border-top: none; }
.dev-resp-label {
padding: 0.2rem 0.6rem 0.05rem;
font-size: 0.42rem;
font-weight: 700;
color: var(--d-text3);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.dev-pos-item { display: flex; align-items: center; gap: 0.2rem; }
.dev-pos-item label {
font-size: 0.42rem; font-weight: 700; color: var(--d-text3); width: 14px;
}
.dev-pos-item input {
flex: 1; padding: 0.12rem 0.2rem; border-radius: 3px; border: 1px solid var(--d-border);
background: var(--d-bg3); color: var(--d-text); font-size: 0.46rem; text-align: center;
outline: none; width: 0;
}
.dev-pos-item input:focus { border-color: var(--d-accent); }
/* 토글 */
.dev-toggle {
width: 26px; height: 14px; border-radius: 7px; background: var(--d-border);
position: relative; cursor: pointer; transition: background 0.12s; flex-shrink: 0;
}
.dev-toggle.on { background: var(--d-accent); }
.dev-toggle::after {
content: ''; position: absolute; width: 10px; height: 10px; border-radius: 50%;
background: #fff; top: 2px; left: 2px; transition: left 0.12s;
}
.dev-toggle.on::after { left: 14px; }
/* 필드 목록 */
.dev-field-list { padding: 0 0.6rem 0.2rem; max-height: 280px; overflow-y: auto; }
.dev-field-item {
display: flex; align-items: center; gap: 0.3rem; padding: 0.18rem 0;
font-size: 0.48rem; color: var(--d-text2); border-bottom: 1px dashed var(--d-border);
cursor: pointer; transition: background 0.08s; border-radius: 2px;
}
.dev-field-item:last-child { border-bottom: none; }
.dev-field-item:hover { background: var(--d-surface); }
.dev-field-check {
width: 14px; height: 14px; border-radius: 3px; border: 1.5px solid var(--d-border2);
display: flex; align-items: center; justify-content: center; font-size: 0.42rem;
cursor: pointer; transition: all 0.1s; flex-shrink: 0; color: transparent;
}
.dev-field-check.on { background: var(--d-accent); border-color: var(--d-accent); color: #fff; }
.dev-field-name { flex: 1; font-weight: 500; color: var(--d-text); font-size: 0.48rem; }
.dev-field-type {
font-size: 0.4rem; color: var(--d-text3); font-weight: 600;
padding: 0.08rem 0.25rem; border-radius: 3px; background: var(--d-surface);
}
.dev-field-drag { color: var(--d-text3); cursor: grab; font-size: 0.5rem; }
/* 필드 배지 */
.dev-fc-badge {
font-size: 0.36rem; font-weight: 700; padding: 0.04rem 0.18rem; border-radius: 2px;
letter-spacing: 0.02em;
}
.dev-fc-badge.pk { background: var(--d-accent); color: #fff; }
.dev-fc-badge.req { background: var(--d-red); color: #fff; }
.dev-fc-badge.sch { background: var(--d-green); color: #fff; }
.dev-fc-badge.sys { background: var(--d-text3); color: #fff; }
.dev-fc-badge.cmp { background: var(--d-orange); color: #fff; }
/* ═══ 상태바 ═══ */
.dev-status {
display: flex; align-items: center; justify-content: space-between;
padding: 0 0.8rem; height: 22px; font-size: 0.42rem; color: var(--d-text3);
background: var(--d-bg2); border-top: 1px solid var(--d-border);
flex-shrink: 0;
}
/* ═══ 빈 캔버스 ═══ */
.dev-empty {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
text-align: center; color: var(--d-text3);
}
.dev-empty-icon { font-size: 2rem; margin-bottom: 0.4rem; opacity: 0.3; }
.dev-empty-text { font-size: 0.6rem; font-weight: 500; }
/* ═══ 프리뷰 테이블 ═══ */
.dev-pv-table { width: 100%; border-collapse: collapse; font-size: 0.48rem; }
.dev-pv-table th {
text-align: left; padding: 0.22rem 0.35rem; font-weight: 700; color: var(--d-text3);
border-bottom: 1px solid var(--d-border); font-size: 0.42rem; text-transform: uppercase;
}
.dev-pv-table td {
padding: 0.22rem 0.35rem; border-bottom: 1px dashed var(--d-border); color: var(--d-text2);
}
/* ═══ 프리뷰 폼 ═══ */
.dev-pv-field { display: flex; flex-direction: column; gap: 0.1rem; margin-bottom: 0.35rem; }
.dev-pv-field-label { font-size: 0.42rem; font-weight: 700; color: var(--d-text3); }
.dev-pv-field-input {
padding: 0.22rem 0.35rem; border-radius: 4px; border: 1px solid var(--d-border);
background: var(--d-bg3); font-size: 0.48rem; color: var(--d-text2);
}
/* ═══ 프리뷰 검색 ═══ */
.dev-pv-search { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.dev-pv-search-item { display: flex; flex-direction: column; gap: 0.06rem; }
.dev-pv-search-label { font-size: 0.38rem; font-weight: 600; color: var(--d-text3); }
.dev-pv-search-input {
padding: 0.18rem 0.3rem; border-radius: 3px; border: 1px solid var(--d-border);
background: var(--d-bg3); font-size: 0.44rem; color: var(--d-text2); min-width: 80px;
}
/* ═══ 프리뷰 버튼 ═══ */
.dev-pv-btn {
display: inline-flex; align-items: center; gap: 0.2rem; padding: 0.18rem 0.45rem;
border-radius: 4px; font-size: 0.46rem; font-weight: 600; border: 1px solid var(--d-border);
color: var(--d-text2);
}
.dev-pv-btn.primary { background: var(--d-accent); border-color: var(--d-accent); color: #fff; }
/* ═══ 팝업 오버레이 ═══ */
.dev-popup-overlay {
position: absolute; inset: 0; background: rgba(0,0,0,0.3);
display: flex; align-items: center; justify-content: center; z-index: 20;
}
.dev-popup-frame {
width: 500px; min-height: 300px; border-radius: 8px;
border: 1px solid var(--d-border); background: var(--d-bg2);
position: relative; overflow: hidden;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.dev-popup-frame .dev-canvas-inner {
min-width: unset; min-height: 300px; padding: 12px;
}
/* ═══ 힌트 텍스트 ═══ */
.dev-hint {
font-size: 0.42rem; color: var(--d-text3); padding: 0.15rem 0.6rem; font-style: italic;
}
/* ═══ 삭제 버튼 ═══ */
.dev-delete-btn {
width: 100%; padding: 0.25rem; border-radius: 4px; border: 1px solid var(--d-red);
background: transparent; color: var(--d-red); font-size: 0.46rem; font-weight: 600;
cursor: pointer; transition: all 0.12s;
}
.dev-delete-btn:hover { background: var(--d-red); color: #fff; }