GhiVideo 소스 복제 — v4 작업 시작 기준

기존 GhiVideo 저장소 HEAD의 트래킹 소스 362개 파일을 복제.
(node_modules·storage·빌드 산출물·대용량 미디어는 .gitignore 규칙대로 제외)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 14:32:51 +09:00
co-authored by Claude Fable 5
commit d38b842e8d
362 changed files with 46358 additions and 0 deletions
@@ -0,0 +1,331 @@
$transport-gradient: linear-gradient(
180deg,
rgb(96, 79, 50) 0%,
rgb(85, 75, 49) 7%,
rgb(64, 51, 39) 11%,
rgb(40, 34, 22) 100%
);
$tool-gradient: linear-gradient(
180deg,
rgb(96, 79, 50) 0%,
rgb(59, 52, 34) 7%,
rgb(54, 43, 31) 11%,
rgb(66, 55, 35) 100%
);
$group-border: linear-gradient(
180deg,
rgba(255, 182, 135, 0.5) 0%,
rgba(255, 255, 255, 0) 100%,
);
$default-border: linear-gradient(
180deg,
rgba(255, 255, 255, 0.25) 25%,
rgba(153, 122, 101, 0.63) 63%,
rgba(98, 62, 39, 0.81) 81%,
rgba(80, 67, 60, 1) 100%
);
$hover-border: linear-gradient(
180deg,
#ffa812 8%,
#ffdfa7 38%,
#ffa812 72%,
#a96e09 95%
);
// ── 컨트롤 패널 치수 (이 값들만 바꾸면 관련 위치/크기가 자동 계산됨) ──
$panel-h: 96.67px; // transportGroup 높이
$panel-inset: 2px; // 그룹 가장자리 공통 인셋
// 재생·정지·측점입력 그룹
$transport-group-w: 161.33px;
$transport-btn-w: 78px;
$transport-btn-h: 55px;
$transport-btn-left: 3px; // 첫(재생) 버튼 좌측 인셋
$transport-btn-top: 4px;
$transport-btn-gap: 1px; // 재생 ↔ 정지 간격
// 화면캡처·측점선 그룹
$tool-group-w: 76px;
$tool-group-h: 96px;
$tool-btn-h: 47px;
$tool-btn-top: 0.5px; // 화면캡처(첫 버튼) top — 47×2가 96 안에 들어가도록 축소
$tool-btn-gap: 1px; // 화면캡처 ↔ 측점선 간격 (47px 버튼이 바 안에 들어가도록)
/* 컨트롤 묶음 — bottomBar flex 자식 (좌측 정렬, 세로 중앙) */
.controlsRow {
position: relative;
display: inline-flex;
gap: 4px;
padding-left: 1.33px;
align-items: center;
z-index: 4;
}
%control-button {
position: absolute;
z-index: 4;
box-sizing: border-box;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
border: 1.5px solid transparent;
border-radius: 4px;
appearance: none;
cursor: pointer;
overflow: hidden;
background: $transport-gradient padding-box, $default-border border-box;
box-shadow: 0 0 0 2px rgb(0, 0, 0);
transition: box-shadow 150ms ease-out;
&:hover {
background: $transport-gradient padding-box, $hover-border border-box;
box-shadow:
0 0 0 2px #000,
0 0 8px rgba(255, 168, 18, 0.35);
}
}
/* 플레이·정지·측점 입력 패널 */
.transportGroup {
position: relative;
width: $transport-group-w;
height: $panel-h;
}
.leftPanel {
position: absolute;
left: -1px;
top: 0;
width: calc(100% + 4px);
height: 100%;
/* 그라데이션 테두리 두께 = 이 border 폭 (숫자를 키우면 더 굵어짐) */
border: 2px solid transparent;
box-sizing: border-box;
border-radius: 4px;
background:
linear-gradient(rgb(38, 29, 13), transparent) padding-box,
$group-border border-box;
z-index: 3;
}
.transportBtn {
@extend %control-button;
left: $transport-btn-left;
top: $transport-btn-top;
width: $transport-btn-w;
height: $transport-btn-h;
}
.stopBtn {
@extend %control-button;
// 재생 버튼 우측 + 간격 → 폭이 바뀌면 자동으로 따라옴
left: $transport-btn-left + $transport-btn-w + $transport-btn-gap + 1px;
top: $transport-btn-top;
width: $transport-btn-w;
height: $transport-btn-h;
}
/* 버튼 아이콘: 실제 SVG 파일(그라데이션·그림자·테두리 포함)을 배경으로 사용.
호버 시 버튼의 :hover 로 _on 버전으로 교체한다. 박스 크기는 SVG viewBox의 2/3
(= 기존 렌더 크기 유지). 새 아이콘은 SVG만 교체하면 되어 유지보수가 쉽다. */
%btn-icon {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
pointer-events: none;
}
.playIcon {
@extend %btn-icon;
width: 40px; // 60 × 2/3
height: 40px;
background-image: url('/assets/icons/ico_play.png');
}
.transportBtn:hover .playIcon {
background-image: url('/assets/icons/ico_play_on.png');
}
.pauseIcon {
@extend %btn-icon;
width: 33.33px; // 50 × 2/3
height: 33.33px;
background-image: url('/assets/icons/icon_pause.png');
}
.transportBtn:hover .pauseIcon {
background-image: url('/assets/icons/icon_pause_on.png');
}
.stopIcon {
@extend %btn-icon;
width: 40px; // 60 × 2/3
height: 40px;
background-image: url('/assets/icons/icon_stop.png');
}
.stopBtn:hover .stopIcon {
background-image: url('/assets/icons/icon_stop_on.png');
}
.mileageInput {
position: absolute;
left: $transport-btn-left;
top: 62px;
z-index: 4;
width: $transport-group-w - $transport-btn-left - $panel-inset;
height: 32.67px;
border: none;
border-radius: 4px;
background: rgba(0, 0, 0, 0.65);
box-shadow:
0 0 0 2px rgb(0, 0, 0),
0 0.5px 0 0 rgba(255, 255, 255, 0.1);
box-sizing: border-box;
padding: 0 8px;
font-family: 'Noto Sans KR', var(--font-ui);
font-size: 16px;
font-weight: 400;
letter-spacing: 0.04em;
color: #fff;
&::placeholder {
color: rgba(255, 255, 255, 0.5);
}
&:focus {
outline: none;
}
}
/* '측점 없음' 안내 — 입력창 위에 겹쳐 표시(패널 자식이 absolute 라 일반 흐름은 가려짐). */
.notFound {
position: absolute;
left: $transport-btn-left;
top: 62px;
height: 32.67px;
width: $transport-group-w - $transport-btn-left - $panel-inset;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
z-index: 6; // mileageInput(z:4) 위
border-radius: 4px;
background: rgba(20, 0, 0, 0.88);
box-shadow: 0 0 0 2px #000;
color: #ff6b6b;
font-family: 'Noto Sans KR', var(--font-ui);
font-size: 14px;
font-weight: 700;
letter-spacing: 0.02em;
white-space: nowrap;
pointer-events: none;
}
/* 화면캡처·측점선 보기 패널 */
.toolGroup {
position: relative;
width: $tool-group-w;
height: $tool-group-h;
margin-left: 1px; // transportGroup 패널 확장분만큼 1px 우측 이동
border-radius: 4px;
background: #1a1309;
}
.toolBtn {
@extend %control-button;
left: $panel-inset;
width: $tool-group-w - 2 * $panel-inset; // 좌우 인셋 제외 → 그룹 폭 바뀌면 자동
height: $tool-btn-h; // 바 영역을 벗어나지 않도록 축소 + 두 버튼 사이 간격 확보
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1px;
background: $tool-gradient padding-box, $default-border border-box;
&:hover {
background: $tool-gradient padding-box, $hover-border border-box;
box-shadow:
0 0 0 2px #000,
0 0 8px rgba(255, 168, 18, 0.35);
}
}
.captureBtn {
top: $tool-btn-top + $tool-btn-top;
}
/* 화면캡처 아래 = 첫 버튼 top + 높이 + 간격 → 높이/간격 바꾸면 자동으로 따라옴 */
.lineBtn {
top: $tool-btn-top + $tool-btn-h + $tool-btn-gap + $tool-btn-top + 1px;
}
.cameraIcon {
@extend %btn-icon;
width: 24px; // 확대
height: 20px;
background-image: url('/assets/icons/icon-camera-body.png');
}
.captureBtn:hover .cameraIcon {
background-image: url('/assets/icons/icon-camera-body_on.png');
}
.lineIcon {
@extend %btn-icon;
width: 19.2px; // 확대
height: 20px;
background-image: url('/assets/icons/icon-sturn.png');
}
.lineBtn:hover .lineIcon {
background-image: url('/assets/icons/icon-sturn_on.png');
}
.toolLabel {
font-family: 'Noto Sans KR', var(--font-ui);
font-weight: 700;
font-size: 13.33px; // 살짝 축소 (기존 14.67)
line-height: 1.2;
letter-spacing: -0.04em;
white-space: nowrap;
color: rgb(204, 199, 189);
text-shadow: 0 0.5px 1px rgb(0, 0, 0);
}
.toolBtn:hover .toolLabel {
color: #ffa812;
}
/* 측점선 보기 토글 ON 상태(클릭): 호버와 동일한 아이콘·라벨 색에,
눌린 어두운 그라데이션 배경 + 주황 그라데이션 테두리. 별도 동작 없음.
(채움 레이어는 padding-box, 테두리 그라데이션은 border-box) */
.lineBtn.active {
background:
linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.1) 100%) padding-box,
linear-gradient(
180deg,
#211601 5%,
#211601 10%,
#352509 15%,
#352509 85%,
#231700 90%,
#000 95%
) padding-box,
$hover-border border-box;
box-shadow: 0 0 0 2px #000;
}
.lineBtn.active .lineIcon {
background-image: url('/assets/icons/icon-sturn_on.png');
}
.lineBtn.active .toolLabel {
color: #ffa812;
}