Files
GhiVideo_v4/client/src/stationbar/components/Timeline/Timeline.module.scss
T
b23042andClaude Fable 5 d38b842e8d GhiVideo 소스 복제 — v4 작업 시작 기준
기존 GhiVideo 저장소 HEAD의 트래킹 소스 362개 파일을 복제.
(node_modules·storage·빌드 산출물·대용량 미디어는 .gitignore 규칙대로 제외)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:32:51 +09:00

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;
}