fix(css): isolate 6 more cross-screen class collisions (audit sweep)

kx-lightbox(gallery->kx-glightbox), kx-switch(editor->kx-eswitch),
kx-dot(editor->kx-edot, visitor->kx-vdot), kx-toggle(auction->kx-atoggle),
kx-compare(auction->kx-bidcmp), kx-stepper(contractor->kx-cstepper).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
zio 2026-07-24 07:52:01 +09:00
parent 7d9982d566
commit b7b28b88a6
11 changed files with 86 additions and 86 deletions

View File

@ -268,13 +268,13 @@ export function AuctionDetailPage() {
</h2>
<button
type="button"
className="kx-toggle"
className="kx-atoggle"
aria-pressed={anon}
onClick={() => setAnon((v) => !v)}
>
<span className={`kx-toggle__track ${anon ? 'is-on' : ''}`}>
<span className="kx-toggle__knob" />
<span className={`kx-atoggle__track ${anon ? 'is-on' : ''}`}>
<span className="kx-atoggle__knob" />
</span>
</button>
</div>

View File

@ -158,24 +158,24 @@ export function AwardComparePage() {
<GridPdfButton title="견적서 비교표" columns={comparePdfColumns} rows={comparePdfRows} disabled={quotes.length === 0} />
</div>
<div className="kx-table-scroll">
<table className="kx-compare">
<table className="kx-bidcmp">
<thead>
<tr>
<th className="kx-compare__metric-col"> </th>
<th className="kx-bidcmp__metric-col"> </th>
{quotes.map((qc2) => (
<th
key={qc2.bidId}
className={`kx-compare__co ${qc2.bidId === selected?.bidId ? 'kx-compare__co--rec' : ''}`}
className={`kx-bidcmp__co ${qc2.bidId === selected?.bidId ? 'kx-bidcmp__co--rec' : ''}`}
>
{qc2.recommended && <span className="kx-compare__rec-tag"> </span>}
{qc2.recommended && <span className="kx-bidcmp__rec-tag"> </span>}
<button
type="button"
className="kx-compare__co-select"
className="kx-bidcmp__co-select"
aria-pressed={qc2.bidId === selected?.bidId}
onClick={() => setSelectedBidId(qc2.bidId)}
>
<span className="kx-compare__co-name">{qc2.alias}</span>
<span className="kx-compare__co-sub">{qc2.subtitle}</span>
<span className="kx-bidcmp__co-name">{qc2.alias}</span>
<span className="kx-bidcmp__co-sub">{qc2.subtitle}</span>
</button>
</th>
))}
@ -183,14 +183,14 @@ export function AwardComparePage() {
</thead>
<tbody>
<tr>
<td className="kx-compare__metric-col"> (VAT )</td>
<td className="kx-bidcmp__metric-col"> (VAT )</td>
{quotes.map((qc2) => (
<td key={qc2.bidId} className={qc2.isLowestPrice ? 'kx-compare__best' : ''}>
<span className="kx-compare__val-strong tnum">
<td key={qc2.bidId} className={qc2.isLowestPrice ? 'kx-bidcmp__best' : ''}>
<span className="kx-bidcmp__val-strong tnum">
{formatWon(qc2.totalPrice)}
</span>
<span
className={`kx-compare__note ${qc2.isLowestPrice ? 'kx-compare__note--best' : ''}`}
className={`kx-bidcmp__note ${qc2.isLowestPrice ? 'kx-bidcmp__note--best' : ''}`}
>
{qc2.isLowestPrice ? '최저가' : `+${qc2.priceDeltaPct}% 차이`}
</span>
@ -198,24 +198,24 @@ export function AwardComparePage() {
))}
</tr>
<tr>
<td className="kx-compare__metric-col"> ( )</td>
<td className="kx-bidcmp__metric-col"> ( )</td>
{quotes.map((qc2) => (
<td key={qc2.bidId} className={qc2.isShortestLead ? 'kx-compare__best' : ''}>
<td key={qc2.bidId} className={qc2.isShortestLead ? 'kx-bidcmp__best' : ''}>
<span className="tnum">{qc2.leadDays}</span>
{qc2.isShortestLead && (
<span className="kx-compare__note kx-compare__note--best"> </span>
<span className="kx-bidcmp__note kx-bidcmp__note--best"> </span>
)}
</td>
))}
</tr>
<tr>
<td className="kx-compare__metric-col"> ( 1)</td>
<td className="kx-bidcmp__metric-col"> ( 1)</td>
{quotes.map((qc2) => {
const best =
qc2.reputation === Math.max(...quotes.map((x) => x.reputation));
return (
<td key={qc2.bidId} className={best ? 'kx-compare__best' : ''}>
<span className="kx-compare__star">
<td key={qc2.bidId} className={best ? 'kx-bidcmp__best' : ''}>
<span className="kx-bidcmp__star">
<IconStar size={14} />
</span>{' '}
<span className="tnum">{qc2.reputation.toFixed(1)}</span>
@ -224,16 +224,16 @@ export function AwardComparePage() {
})}
</tr>
<tr>
<td className="kx-compare__metric-col"> </td>
<td className="kx-bidcmp__metric-col"> </td>
{quotes.map((qc2) => (
<td key={qc2.bidId} className={qc2.recommended ? 'kx-compare__score' : ''}>
<td key={qc2.bidId} className={qc2.recommended ? 'kx-bidcmp__score' : ''}>
{qc2.recommended ? (
<span className="kx-compare__score-val tnum">
<span className="kx-bidcmp__score-val tnum">
{qc2.score.toFixed(1)}
</span>
) : (
<span
className="kx-compare__val-strong tnum"
className="kx-bidcmp__val-strong tnum"
style={{ color: 'var(--color-neutral-500)' }}
>
{qc2.score.toFixed(1)}

View File

@ -291,7 +291,7 @@
gap: var(--space-3);
}
/* 비교표 컬럼 선택 버튼(낙찰 대상 선택) — 헤더 텍스트 스택을 버튼으로 감싼다. */
.kx-compare__co-select {
.kx-bidcmp__co-select {
display: flex;
flex-direction: column;
align-items: center;
@ -302,7 +302,7 @@
cursor: pointer;
padding: 0;
}
.kx-compare__co-select[aria-pressed='true'] .kx-compare__co-name {
.kx-bidcmp__co-select[aria-pressed='true'] .kx-bidcmp__co-name {
color: var(--color-primary, var(--color-ai-accent));
}
@ -579,7 +579,7 @@
@media (prefers-reduced-motion: reduce) {
.kx-rank__ws.is-live { animation: none; }
}
.kx-toggle {
.kx-atoggle {
display: inline-flex;
align-items: center;
gap: 8px;
@ -589,7 +589,7 @@
border: none;
cursor: pointer;
}
.kx-toggle__track {
.kx-atoggle__track {
width: 38px;
height: 20px;
border-radius: var(--radius-pill);
@ -597,10 +597,10 @@
position: relative;
transition: background 0.15s ease;
}
.kx-toggle__track.is-on {
.kx-atoggle__track.is-on {
background: var(--color-primary-600);
}
.kx-toggle__knob {
.kx-atoggle__knob {
position: absolute;
top: 2px;
left: 2px;
@ -610,7 +610,7 @@
background: var(--color-white);
transition: transform 0.15s ease;
}
.kx-toggle__track.is-on .kx-toggle__knob {
.kx-atoggle__track.is-on .kx-atoggle__knob {
transform: translateX(18px);
}
@ -771,48 +771,48 @@
}
/* 비교 매트릭스 */
.kx-compare {
.kx-bidcmp {
width: 100%;
border-collapse: collapse;
font-size: var(--fs-body);
}
.kx-compare th,
.kx-compare td {
.kx-bidcmp th,
.kx-bidcmp td {
padding: var(--space-4);
border-bottom: var(--border-card);
text-align: center;
}
.kx-compare thead th {
.kx-bidcmp thead th {
background: var(--color-neutral-050);
font-weight: var(--fw-bold);
color: var(--color-neutral-900);
}
.kx-compare thead th.kx-compare__metric-col,
.kx-compare td.kx-compare__metric-col {
.kx-bidcmp thead th.kx-bidcmp__metric-col,
.kx-bidcmp td.kx-bidcmp__metric-col {
text-align: left;
width: 22%;
font-weight: var(--fw-semibold);
color: var(--color-neutral-700);
}
.kx-compare__co {
.kx-bidcmp__co {
position: relative;
}
.kx-compare__co-name {
.kx-bidcmp__co-name {
font-size: var(--fs-h3);
font-weight: var(--fw-bold);
color: var(--color-neutral-900);
}
.kx-compare__co--rec .kx-compare__co-name {
.kx-bidcmp__co--rec .kx-bidcmp__co-name {
color: var(--color-primary-700);
}
.kx-compare__co-sub {
.kx-bidcmp__co-sub {
display: block;
font-size: var(--fs-micro);
font-weight: var(--fw-regular);
color: var(--color-neutral-500);
margin-top: 2px;
}
.kx-compare__rec-tag {
.kx-bidcmp__rec-tag {
display: inline-block;
margin-bottom: 6px;
font-size: var(--fs-nano);
@ -822,33 +822,33 @@
padding: 2px 8px;
border-radius: var(--radius-pill);
}
.kx-compare__best {
.kx-bidcmp__best {
background: #f0fbf6;
color: var(--color-success);
font-weight: var(--fw-bold);
}
.kx-compare__val-strong {
.kx-bidcmp__val-strong {
font-size: var(--fs-h3);
font-weight: var(--fw-bold);
}
.kx-compare__note {
.kx-bidcmp__note {
display: block;
font-size: var(--fs-micro);
color: var(--color-neutral-500);
margin-top: 2px;
}
.kx-compare__note--best {
.kx-bidcmp__note--best {
color: var(--color-success);
}
.kx-compare__score {
.kx-bidcmp__score {
background: var(--color-ai-surface);
}
.kx-compare__score-val {
.kx-bidcmp__score-val {
font-size: var(--fs-display);
font-weight: var(--fw-bold);
color: var(--color-ai-accent);
}
.kx-compare__star {
.kx-bidcmp__star {
color: var(--color-warning);
}

View File

@ -116,13 +116,13 @@
}
/* 시공 스테퍼 */
.kx-stepper {
.kx-cstepper {
position: relative;
display: flex;
justify-content: space-between;
margin: var(--space-5) var(--space-2) var(--space-5);
}
.kx-stepper::before {
.kx-cstepper::before {
content: '';
position: absolute;
top: 16px;

View File

@ -501,13 +501,13 @@ export function BoothLayoutEditorPage() {
{summary && (
<>
<span> {summary.boothCount} / {summary.targetBoothCount}</span>
<span className="kx-dot" />
<span className="kx-edot" />
<span> {summary.salesAreaM2.toLocaleString()}</span>
</>
)}
{localBooths.length > 0 && (
<>
<span className="kx-dot" />
<span className="kx-edot" />
<span className="kx-editor__local-count"> {localBooths.length}()</span>
<Button size="sm" onClick={saveLocal} disabled={saving}>
{saving ? '저장 중…' : '배치 저장'}
@ -769,10 +769,10 @@ function LayerToggle({ label, on, onToggle }: { label: string; on: boolean; onTo
role="switch"
aria-checked={on}
aria-label={label}
className={`kx-switch ${on ? 'is-on' : ''}`}
className={`kx-eswitch ${on ? 'is-on' : ''}`}
onClick={onToggle}
>
<span className="kx-switch__knob" />
<span className="kx-eswitch__knob" />
</button>
</label>
);

View File

@ -208,7 +208,7 @@
font-size: var(--fs-body);
color: var(--color-neutral-700);
}
.kx-switch {
.kx-eswitch {
width: 34px;
height: 18px;
border-radius: var(--radius-pill);
@ -217,10 +217,10 @@
position: relative;
transition: background 0.15s ease;
}
.kx-switch.is-on {
.kx-eswitch.is-on {
background: var(--color-primary-600);
}
.kx-switch__knob {
.kx-eswitch__knob {
position: absolute;
top: 2px;
left: 2px;
@ -230,7 +230,7 @@
background: var(--color-white);
transition: left 0.15s ease;
}
.kx-switch.is-on .kx-switch__knob {
.kx-eswitch.is-on .kx-eswitch__knob {
left: 18px;
}
@ -352,7 +352,7 @@
background: var(--color-ai-surface, #efeaff);
color: var(--color-ai-accent, #6d4aff);
}
.kx-dot {
.kx-edot {
width: 3px;
height: 3px;
border-radius: 50%;

View File

@ -133,15 +133,15 @@ export function VisualizationGalleryPage() {
{lightbox && (
<div className="kx-modal" role="dialog" aria-modal="true" aria-label="이미지 상세">
<div className="kx-modal__backdrop" onClick={() => setLightbox(null)} />
<div className="kx-lightbox">
<div className="kx-glightbox">
<button
className="kx-lightbox__close"
className="kx-glightbox__close"
onClick={() => setLightbox(null)}
aria-label="닫기"
>
</button>
<div className="kx-lightbox__stage">
<div className="kx-glightbox__stage">
{lightbox.shotPreset === 'S5' ? (
<CompareSlider
beforeSrc={BEFORE_EMPTY_HALL}
@ -168,8 +168,8 @@ export function VisualizationGalleryPage() {
/>
)}
</div>
<div className="kx-lightbox__meta">
<dl className="kx-lightbox__dl mono">
<div className="kx-glightbox__meta">
<dl className="kx-glightbox__dl mono">
<div>
<dt></dt>
<dd>{new Date(lightbox.createdAt).toLocaleString('ko-KR')}</dd>
@ -183,7 +183,7 @@ export function VisualizationGalleryPage() {
<dd>{lightbox.schemaHash ?? '—'}</dd>
</div>
</dl>
<p className="kx-lightbox__notice">{lightbox.notice}</p>
<p className="kx-glightbox__notice">{lightbox.notice}</p>
</div>
</div>
</div>

View File

@ -112,7 +112,7 @@
}
/* 라이트박스 */
.kx-lightbox {
.kx-glightbox {
position: relative;
z-index: 1;
width: min(880px, 94vw);
@ -123,7 +123,7 @@
padding: var(--space-5);
box-shadow: var(--shadow-level2);
}
.kx-lightbox__close {
.kx-glightbox__close {
position: absolute;
top: var(--space-3);
right: var(--space-3);
@ -134,25 +134,25 @@
height: 32px;
color: var(--color-neutral-700);
}
.kx-lightbox__stage {
.kx-glightbox__stage {
margin-bottom: var(--space-4);
}
.kx-lightbox__dl {
.kx-glightbox__dl {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: var(--space-3);
font-size: var(--fs-caption);
}
.kx-lightbox__dl dt {
.kx-glightbox__dl dt {
color: var(--color-neutral-500);
margin-bottom: 2px;
}
.kx-lightbox__dl dd {
.kx-glightbox__dl dd {
margin: 0;
color: var(--color-neutral-900);
word-break: break-all;
}
.kx-lightbox__notice {
.kx-glightbox__notice {
margin-top: var(--space-3);
font-size: var(--fs-micro);
color: var(--color-neutral-500);

View File

@ -311,7 +311,7 @@ function LeadDetail({
<ul className="kx-vis__activity">
{lead.activity.map((a, i) => (
<li key={i}>
<span className="kx-dot" />
<span className="kx-vdot" />
<span className="kx-vis__activity-text">{a.text}</span>
<span className="kx-vis__activity-at">{a.at}</span>
</li>

View File

@ -166,10 +166,10 @@ export function VisitorRegistrationDashboardPage() {
<h2><IconTrendUp className="kx-title-ic" size={18} /> · </h2>
<ul className="kx-vis__legend" aria-hidden="true">
<li>
<span className="kx-dot" style={{ background: CHART.primary600 }} />
<span className="kx-vdot" style={{ background: CHART.primary600 }} />
</li>
<li>
<span className="kx-dot" style={{ background: CHART.slate }} />
<span className="kx-vdot" style={{ background: CHART.slate }} />
</li>
</ul>
</div>
@ -218,7 +218,7 @@ export function VisitorRegistrationDashboardPage() {
<ul>
{summary.types.map((s) => (
<li key={s.name}>
<span className="kx-dot" style={{ background: s.color }} />
<span className="kx-vdot" style={{ background: s.color }} />
{s.name}
<strong className="tnum">{s.value}%</strong>
</li>
@ -237,7 +237,7 @@ export function VisitorRegistrationDashboardPage() {
{summary.forms.map((f) => (
<li key={f.id} className="kx-vis__form">
<span className="kx-vis__form-name">
<span className={`kx-dot ${f.active ? 'is-on' : 'is-off'}`} />
<span className={`kx-vdot ${f.active ? 'is-on' : 'is-off'}`} />
{f.name}
</span>
<span className="kx-vis__form-count tnum">{f.count.toLocaleString()}</span>
@ -460,7 +460,7 @@ function CheckinPill({ state }: { state: string }) {
const s = (KNOWN_CHECKIN as string[]).includes(state) ? (state as CheckinState) : 'waiting';
return (
<span className={`kx-vis__checkin is-${s}`}>
<span className="kx-dot" /> {CHECKIN_LABEL[s]}
<span className="kx-vdot" /> {CHECKIN_LABEL[s]}
</span>
);
}

View File

@ -84,7 +84,7 @@
}
/* 도트 */
.kx-dot {
.kx-vdot {
width: 8px;
height: 8px;
border-radius: 50%;
@ -92,10 +92,10 @@
flex-shrink: 0;
background: var(--color-neutral-500);
}
.kx-dot.is-on {
.kx-vdot.is-on {
background: var(--color-primary-600);
}
.kx-dot.is-off {
.kx-vdot.is-off {
background: var(--color-neutral-200);
}
@ -442,19 +442,19 @@
.kx-vis__checkin.is-done {
color: var(--color-success);
}
.kx-vis__checkin.is-done .kx-dot {
.kx-vis__checkin.is-done .kx-vdot {
background: var(--color-success);
}
.kx-vis__checkin.is-waiting {
color: var(--color-neutral-500);
}
.kx-vis__checkin.is-waiting .kx-dot {
.kx-vis__checkin.is-waiting .kx-vdot {
background: var(--color-neutral-500);
}
.kx-vis__checkin.is-cancelled {
color: var(--color-error);
}
.kx-vis__checkin.is-cancelled .kx-dot {
.kx-vis__checkin.is-cancelled .kx-vdot {
background: var(--color-error);
}
@ -734,7 +734,7 @@
align-items: baseline;
gap: var(--space-2);
}
.kx-vis__activity .kx-dot {
.kx-vis__activity .kx-vdot {
background: var(--color-ai-accent);
align-self: center;
}