기존 GhiVideo 저장소 HEAD의 트래킹 소스 362개 파일을 복제. (node_modules·storage·빌드 산출물·대용량 미디어는 .gitignore 규칙대로 제외) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
321 lines
6.5 KiB
SCSS
Executable File
321 lines
6.5 KiB
SCSS
Executable File
/* 트랙 본체 가로 압축 래퍼. 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)가 위/아래 글자수 균등하게 <br>로 처리.
|
|
박스는 내용 폭에 맞게(최대 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;
|
|
}
|