/* 단가산출 다이어그램 — 자유 배치 */ .m02-dfdg { --cfdg-in: var(--color-accent); --cfdg-out: #c2610c; display: flex; flex-direction: column; gap: var(--spacing-8); min-width: 0; font-size: var(--text-body-sm); } :root[data-theme="dark"] .m02-dfdg { --cfdg-out: #e5a866; --cfdg-lift: #fff; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .m02-dfdg { --cfdg-out: #e5a866; --cfdg-lift: #fff; } } .m02-dfdg__bar, .m02-dfdg__tools { display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-8); } .m02-dfdg__tools { min-height: 1.75rem; } .m02-dfdg__title { font-size: var(--text-body); } .m02-dfdg__gap { flex: 1; } .m02-dfdg__zoom { min-width: 3rem; text-align: center; font-variant-numeric: tabular-nums; color: var(--color-text-secondary); } .m02-dfdg__note { color: var(--color-danger); } .m02-dfdg__hint { margin: 0; color: var(--color-text-muted); } /* 판 — 화면 위쪽 큰 영역 */ .m02-dfdg__view { position: relative; height: max(360px, 55vh); overflow: hidden; border: 1px solid var(--color-border); border-radius: var(--radius-lg); background-color: var(--color-canvas); background-image: radial-gradient( color-mix(in srgb, var(--color-text-muted) 35%, transparent) 1px, transparent 1px ); background-size: 20px 20px; cursor: default; touch-action: none; /* 왼쪽 끌기로 글자가 잡히지 않게 */ user-select: none; -webkit-user-select: none; } .m02-dfdg__view.is-panning { cursor: grabbing; } .m02-dfdg__world { position: absolute; inset: 0 auto auto 0; width: 1px; height: 1px; transform-origin: 0 0; } .m02-dfdg__lines { position: absolute; inset: 0 auto auto 0; width: 1px; height: 1px; overflow: visible; pointer-events: none; } /* 상자 — 종류별 기본 색 · 상자마다 바꾼 색(--cf-color) */ .m02-dfdg__box { position: absolute; display: flex; flex-direction: column; gap: var(--spacing-4); min-width: 11rem; max-width: 16rem; padding: 0 0 var(--spacing-8); border: 1px solid color-mix(in srgb, var(--cf-color) 55%, var(--color-border)); border-radius: var(--radius-lg); background: var(--color-surface); box-shadow: 0 1px 3px rgb(0 0 0 / 12%); cursor: pointer; } .m02-dfdg__box:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; } .m02-dfdg__box.is-selected { border: 2px solid var(--cf-color); box-shadow: 0 0 0 3px color-mix(in srgb, var(--cf-color) 22%, transparent); } .m02-dfdg__head { display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 0 var(--spacing-8); padding: var(--spacing-4) var(--spacing-8); border-radius: var(--radius-lg) var(--radius-lg) 0 0; background: color-mix(in srgb, var(--cf-color) 16%, var(--color-surface)); cursor: move; } .m02-dfdg__kind { grid-column: 1; /* 어두운 화면은 종류 색을 밝게 섞음 */ color: color-mix(in srgb, var(--cf-color) 55%, var(--cfdg-lift, var(--cf-color))); font-size: var(--text-caption); font-weight: 600; } .m02-dfdg__unit { grid-column: 2; grid-row: 1; color: var(--color-text-secondary); font-size: var(--text-caption); } .m02-dfdg__name { grid-column: 1 / -1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .m02-dfdg__pills { display: flex; justify-content: space-between; gap: var(--spacing-8); padding: 0 var(--spacing-8); } .m02-dfdg__ins, .m02-dfdg__outs { display: flex; flex-direction: column; gap: var(--spacing-4); min-width: 0; } .m02-dfdg__outs { align-items: flex-end; } /* 알약 — 받는 파랑 · 내보내는 주황 · 빈 자리 회색 */ .m02-dfdg__pill { display: inline-flex; align-items: baseline; gap: var(--spacing-4); max-width: 9rem; padding: 0 var(--spacing-8); border: 1px solid var(--cfdg-in); border-radius: 999px; background: color-mix(in srgb, var(--cfdg-in) 12%, var(--color-surface)); color: var(--cfdg-in); font: inherit; font-size: var(--text-caption); cursor: pointer; white-space: nowrap; } .m02-dfdg__pill.is-out { border-color: var(--cfdg-out); background: color-mix(in srgb, var(--cfdg-out) 14%, var(--color-surface)); color: var(--cfdg-out); cursor: grab; touch-action: none; } .m02-dfdg__pill.is-empty { border-style: dashed; border-color: var(--color-text-muted); background: transparent; color: var(--color-text-muted); } .m02-dfdg__pill.is-chosen { box-shadow: 0 0 0 2px currentColor; } .m02-dfdg.is-linking .m02-dfdg__pill.is-target { box-shadow: 0 0 0 2px var(--cfdg-in); } .m02-dfdg__pill-name { overflow: hidden; text-overflow: ellipsis; } .m02-dfdg__pill-value { color: var(--color-text-secondary); font-variant-numeric: tabular-nums; } .m02-dfdg__pill-value:empty { display: none; } /* 선 — 굽은 선 */ .m02-dfdg__link { pointer-events: auto; cursor: pointer; } .m02-dfdg__hit { fill: none; stroke: transparent; stroke-width: 12; } .m02-dfdg__wire { fill: none; stroke: var(--color-text-muted); stroke-width: 1.5; } .m02-dfdg__link.is-strong .m02-dfdg__wire { stroke: var(--color-accent); stroke-width: 3; } .m02-dfdg__link.is-dim .m02-dfdg__wire { opacity: 0.25; } .m02-dfdg__drag { fill: none; stroke: var(--cfdg-out); stroke-width: 2; stroke-dasharray: 5 4; } /* 함수 상자 흐린 「+」 받는 자리 */ .m02-dfdg__pill.is-plus { justify-content: center; min-width: 2rem; border-style: dashed; border-color: var(--color-text-muted); background: transparent; color: var(--color-text-muted); opacity: 0.7; } .m02-dfdg__pill.is-plus:hover, .m02-dfdg.is-linking .m02-dfdg__pill.is-plus.is-target { opacity: 1; border-color: var(--cfdg-in); color: var(--cfdg-in); } /* ── 흐름도 더함: 선 규칙 이름표 · 흐름 길 · 끊긴 곳 · 알약 고름 ── */ .m02-dfdg { --dfdg-back: var(--color-accent); --dfdg-fwd: #c2410c; } :root[data-theme="dark"] .m02-dfdg { --dfdg-fwd: #f0a35e; } @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .m02-dfdg { --dfdg-fwd: #f0a35e; } } .m02-dfdg__rule rect { fill: var(--color-surface); stroke: var(--color-border); } .m02-dfdg__rule text { fill: var(--color-text-secondary); font-size: 11px; } .m02-dfdg__link.is-strong .m02-dfdg__rule rect { stroke: var(--color-accent); } /* 흐름 길 — 색 + 굵기 + 점선(색만으로 뜻을 안 전함) */ .m02-dfdg__link.is-back .m02-dfdg__wire { stroke: var(--dfdg-back); stroke-width: 3.5; } .m02-dfdg__link.is-fwd .m02-dfdg__wire { stroke: var(--dfdg-fwd); stroke-width: 3.5; stroke-dasharray: 8 5; } .m02-dfdg__link.is-dim .m02-dfdg__wire, .m02-dfdg__link.is-dim .m02-dfdg__rule { opacity: 0.25; } .m02-dfdg__box.is-back { outline: 3px solid var(--dfdg-back); outline-offset: 2px; } .m02-dfdg__box.is-fwd { outline: 3px dashed var(--dfdg-fwd); outline-offset: 2px; } .m02-dfdg__box.is-dim { opacity: 0.45; } .m02-dfdg__pill.is-back { box-shadow: 0 0 0 2px var(--dfdg-back); } .m02-dfdg__pill.is-fwd { outline: 2px dashed var(--dfdg-fwd); outline-offset: 1px; } .m02-dfdg__legend { padding: 0 var(--spacing-8); border-radius: 999px; font-size: var(--text-caption); } .m02-dfdg__legend.is-back { border: 2px solid var(--dfdg-back); color: var(--dfdg-back); } .m02-dfdg__legend.is-fwd { border: 2px dashed var(--dfdg-fwd); color: var(--dfdg-fwd); } /* 고른 알약 테두리 */ .m02-dfdg__pill.is-chosen { box-shadow: 0 0 0 2px var(--color-text); } /* 끊긴 곳 */ .m02-dfdg__warn { position: absolute; top: -9px; right: -9px; width: 18px; height: 18px; padding: 0; border: 0; border-radius: 50%; background: var(--color-danger); color: #fff; font: inherit; font-size: 12px; font-weight: 700; line-height: 18px; cursor: pointer; } .m02-dfdg__pill.is-broken { border-color: var(--color-danger); } .m02-dfdg__pill-warn { color: var(--color-danger); font-weight: 700; } .m02-dfdg__linkwarn circle { fill: var(--color-danger); } .m02-dfdg__linkwarn text { fill: #fff; font-size: 11px; font-weight: 700; } /* 기댐 선 — 규칙이 기대는 알약 → 그 규칙이 있는 곳 · 흐린 점선 + 화살 */ .m02-dfdg__dep { pointer-events: auto; cursor: pointer; } .m02-dfdg__depwire { fill: none; stroke: color-mix(in srgb, var(--color-text-muted) 70%, transparent); stroke-width: 1.5; stroke-dasharray: 3 4; } .m02-dfdg__arrow { fill: var(--color-text-muted); } .m02-dfdg__deptext rect { fill: var(--color-canvas); stroke: none; opacity: 0.85; } .m02-dfdg__deptext text { fill: var(--color-text-muted); font-size: 10px; } .m02-dfdg__dep.is-strong .m02-dfdg__depwire { stroke: var(--color-accent); stroke-width: 2.5; } .m02-dfdg__dep.is-back .m02-dfdg__depwire { stroke: var(--dfdg-back); stroke-width: 2.5; } .m02-dfdg__dep.is-fwd .m02-dfdg__depwire { stroke: var(--dfdg-fwd); stroke-width: 2.5; } .m02-dfdg__dep.is-dim { opacity: 0.25; } /* 쉼 — 조건이 안 맞아 이번엔 안 지나감(문제 아님) */ .m02-dfdg__link.is-rest .m02-dfdg__wire { stroke: var(--color-text-muted); stroke-width: 1.5; stroke-dasharray: 2 5; } .m02-dfdg__restmark rect { fill: var(--color-surface); stroke: var(--color-text-muted); stroke-dasharray: 2 2; } .m02-dfdg__restmark text { fill: var(--color-text-muted); font-size: 10px; } .m02-dfdg__box.is-rest { opacity: 0.5; } /* 빈 판 안내 — 가운데 세 줄 */ .m02-dfdg__empty { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); margin: 0; padding: var(--spacing-16) var(--spacing-24); list-style: none; border: 1px dashed var(--color-border); border-radius: var(--radius-lg); background: var(--color-surface); color: var(--color-text-secondary); display: flex; flex-direction: column; gap: var(--spacing-8); pointer-events: none; } /* 쉼 — 받는 알약만(상자 흐림은 들어오는 선이 모두 쉼일 때) */ .m02-dfdg__pill.is-in.is-rest { border-style: dashed; border-color: var(--color-text-muted); color: var(--color-text-muted); opacity: 0.7; }