/* 트랙 본체 가로 압축 래퍼. transform(translateX·scaleX)은 route.ts 단일 소스에서 파생되어 Timeline.tsx 에서 인라인으로 적용된다(여기에 하드코딩하지 않음). */ .trackWrapper { position: absolute; left: 0; top: 0; width: 1920px; height: 100px; transform-origin: 0 0; pointer-events: none; } .trackFade { position: absolute; left: 0; top: -23px; width: 1920px; height: 23px; background: linear-gradient( 180deg, rgba(102, 102, 102, 0) 0%, rgba(41, 41, 41, 0.25) 50%, rgba(0, 0, 0, 0.5) 100% ); pointer-events: none; } .trackFrame { position: absolute; left: var(--track-x); top: 27.5px; width: var(--track-w); height: 48px; border-radius: 4px; background: linear-gradient(180deg, rgb(11, 8, 3) 0%, rgb(40, 26, 7) 100%); box-shadow: -1px 1px 1px 0 rgb(71, 61, 39); } .trackFrameStripesH { position: absolute; left: var(--track-x); top: 27.5px; width: var(--track-w); height: 48px; border-radius: 4px; background: repeating-linear-gradient( 180deg, rgba(255, 255, 255, 0.07) 0, rgba(255, 255, 255, 0.07) 1px, transparent 1px, transparent 33.33% ); pointer-events: none; } .trackFrameStripesV { position: absolute; left: var(--track-x); top: 31.5px; width: var(--track-w); height: 45.5px; background: repeating-linear-gradient( 90deg, rgba(61, 52, 34, 0.55) 0, rgba(61, 52, 34, 0.55) 2px, transparent 2px, transparent 36.65px ); pointer-events: none; } .trackBase { position: absolute; left: var(--track-x); top: 34.5px; width: var(--track-w); height: 12px; border-radius: 6px; background: rgb(122, 122, 122); z-index: 1; } /* 역명 레이어: 스테이지 전체를 덮어 자식들의 위치 기준이 된다. --track-start / --track-end (트랙 시작·끝, 단일 소스)을 상속받아 아래 역 라벨들이 트랙 가장자리 기준 calc()로 배치된다(시작/끝 바꾸면 자동 이동). */ .stationLayer { position: absolute; inset: 0; pointer-events: none; } /* 트랙 가장자리 기준 오프셋(좌우 대칭): 텍스트 안쪽끝 40px · 점 32/27px · 점선 23/5px */ .stationStart { position: absolute; /* 오른쪽(안쪽) 끝을 '트랙 시작-40px'에 고정 → 대전과 트랙 중앙 기준 좌우 대칭. 글자 폭과 무관하게 점선·텍스트 공백이 양쪽 동일(점까지 8px, 트랙까지 50px). */ right: calc(100% - var(--track-start) + 40px); top: 31px; font-size: 16px; font-weight: 700; color: var(--color-station); white-space: nowrap; } .stationEnd { position: absolute; /* 우측 끝(1920) 기준으로 고정 → 긴 역명(대전조차장 등)도 안쪽(좌)으로 펼쳐져 화면 밖으로 안 잘림. */ right: 8px; top: 31px; font-size: 16px; font-weight: 700; color: var(--color-station); white-space: nowrap; } .dotStart, .dotEnd { position: absolute; top: 39px; width: 5px; height: 5px; border-radius: 50%; background: var(--color-station-dot); } .dotStart { left: calc(var(--track-start) - 32px); } .dotEnd { left: calc(var(--track-end) + 27px); } .leaderStart, .leaderEnd { position: absolute; top: 40.5px; height: 2.5px; width: 18px; background: repeating-linear-gradient( 90deg, var(--color-station-leader) 0 5px, transparent 5px 9px ); } .leaderStart { left: calc(var(--track-start) - 23px); } .leaderEnd { left: calc(var(--track-end) + 5px); } .track { position: absolute; left: var(--track-x); top: 34.5px; width: var(--track-w); height: 12px; z-index: 2; } .leg { position: absolute; top: 0; height: 12px; left: var(--leg-left); width: var(--leg-width); &.down { background: var(--color-track-down); } &.up { background: var(--color-track-up); } } .legPassed { position: absolute; left: 0; top: 0; height: 12px; width: var(--passed-width); .down & { background: var(--gradient-track-down-passed); } .up & { background: var(--gradient-track-up-passed); } } .turnDivider { position: absolute; left: var(--x); top: 0; width: 0; height: 12px; border-left: 1px dashed rgba(0, 0, 0, 0.7); border-right: 1px dashed rgba(255, 255, 255, 0.3); z-index: 3; pointer-events: none; } .mileageRow { position: absolute; left: 0; top: 6px; width: 1920px; height: 13px; white-space: nowrap; z-index: 3; } .labelRow { position: absolute; left: 0; top: 51px; width: 1920px; height: 26px; font-family: 'Noto Sans KR', sans-serif; font-size: 16.5px; font-weight: 800; line-height: 24px; } .zone { position: absolute; left: var(--x); top: -6px; width: var(--zone-width); height: 15.5px; border: 1.5px dashed rgb(218, 203, 185); border-radius: 1px; box-sizing: border-box; pointer-events: none; } .zoneInner { position: absolute; left: var(--x); top: -6px; width: 0; height: 15.5px; border-left: 1.5px dashed rgb(218, 203, 185); pointer-events: none; } .segmentLabel { position: absolute; left: var(--x); top: 2px; transform: translateX(-50%); white-space: nowrap; &.box { transform: none; width: var(--label-width); text-align: center; } /* 텍스트 오른쪽 끝이 left 좌표에 닿음 (시작 라인 왼쪽 배치) */ &.anchorEnd { transform: translateX(-100%); } /* 텍스트 왼쪽 끝이 left 좌표에서 시작 (종료 라인 오른쪽 배치) */ &.anchorStart { transform: none; } &.endpoint { color: #fff; -webkit-text-stroke-width: 4px; -webkit-text-stroke-color: #4f2000; paint-order: stroke fill; } &.accent { color: #ffd4b7; -webkit-text-stroke-width: 2px; -webkit-text-stroke-color: #4f2000; paint-order: stroke fill; } &.neutral { color: #e4e4e4; -webkit-text-stroke-width: 3px; -webkit-text-stroke-color: #0d0d0d; paint-order: stroke fill; } } /* 구조물명: 줄바꿈은 JS(splitStructLabel)가 위/아래 글자수 균등하게
로 처리. 박스는 내용 폭에 맞게(최대 140px) 줄어들어 옆 라벨과 간격을 최대 확보. 각 줄은 nowrap(명시적 줄바꿈만), 아이콘 기준 가운데 정렬. 폰트 크기는 라벨행 기본(16.5px) 유지 — 작게 줄이지 않음. */ .structName { width: max-content; max-width: 140px; white-space: nowrap; text-align: center; line-height: 18px; overflow: hidden; text-overflow: ellipsis; } .seekArea { position: absolute; left: var(--track-x); top: 27.5px; width: var(--track-w); height: 48px; cursor: pointer; z-index: 20; pointer-events: auto; }