diff --git a/src/app/App.tsx b/src/app/App.tsx
index 97528771..b3c370f3 100644
--- a/src/app/App.tsx
+++ b/src/app/App.tsx
@@ -11888,6 +11888,7 @@ export function App({ store, initialStatusMessage }: AppProps) {
}
)
}
+ onSetSequenceDurationMinutes={updateProjectSequenceDurationMinutes}
onAddHeldControlStep={(sequenceId, targetKey) =>
handleAddProjectSequenceControlStep(
sequenceId,
@@ -11904,6 +11905,7 @@ export function App({ store, initialStatusMessage }: AppProps) {
}
onAddDialogueStep={handleAddProjectSequenceDialogueStep}
onDeleteStep={handleDeleteProjectSequenceStep}
+ onSetClipTiming={updateProjectSequenceClipTiming}
onSetControlStepTarget={updateProjectSequenceControlStepTarget}
onSetControlStepEffectOption={
updateProjectSequenceControlStepEffectOption
diff --git a/src/app/ProjectSchedulePane.tsx b/src/app/ProjectSchedulePane.tsx
index fc071a13..a55f48aa 100644
--- a/src/app/ProjectSchedulePane.tsx
+++ b/src/app/ProjectSchedulePane.tsx
@@ -324,10 +324,12 @@ export function ProjectSequencerPane({
onSetActorRoutinePathSpeed,
onSetActorRoutinePathLoop,
onSetSequenceTitle,
+ onSetSequenceDurationMinutes,
onAddHeldControlStep,
onAddImpulseControlStep,
onAddDialogueStep,
onDeleteStep,
+ onSetClipTiming,
onSetControlStepTarget,
onSetControlStepEffectOption,
onSetControlStepNumericValue,
@@ -457,10 +459,12 @@ export function ProjectSequencerPane({
onAddSequence={onAddSequence}
onDeleteSequence={onDeleteSequence}
onSetSequenceTitle={onSetSequenceTitle}
+ onSetSequenceDurationMinutes={onSetSequenceDurationMinutes}
onAddHeldControlStep={onAddHeldControlStep}
onAddImpulseControlStep={onAddImpulseControlStep}
onAddDialogueStep={onAddDialogueStep}
onDeleteStep={onDeleteStep}
+ onSetClipTiming={onSetClipTiming}
onSetControlStepTarget={onSetControlStepTarget}
onSetControlStepEffectOption={onSetControlStepEffectOption}
onSetControlStepNumericValue={onSetControlStepNumericValue}
@@ -736,22 +740,26 @@ export function ProjectSequencerPane({
onSetRoutineStartHour(
selectedRoutine.id,
- Number(event.currentTarget.value)
+ parseTimeOfDayInputHours(
+ event.currentTarget.value,
+ "Clip start"
+ )
)
}
onKeyDown={(event) =>
handleCommitOnEnter(event, () =>
onSetRoutineStartHour(
selectedRoutine.id,
- Number(event.currentTarget.value)
+ parseTimeOfDayInputHours(
+ event.currentTarget.value,
+ "Clip start"
+ )
)
)
}
@@ -762,22 +770,26 @@ export function ProjectSequencerPane({
onSetRoutineEndHour(
selectedRoutine.id,
- Number(event.currentTarget.value)
+ parseTimeOfDayInputHours(
+ event.currentTarget.value,
+ "Clip end"
+ )
)
}
onKeyDown={(event) =>
handleCommitOnEnter(event, () =>
onSetRoutineEndHour(
selectedRoutine.id,
- Number(event.currentTarget.value)
+ parseTimeOfDayInputHours(
+ event.currentTarget.value,
+ "Clip end"
+ )
)
)
}
diff --git a/src/app/app.css b/src/app/app.css
index b3373e1f..5eac4280 100644
--- a/src/app/app.css
+++ b/src/app/app.css
@@ -1119,6 +1119,149 @@ button:disabled {
white-space: nowrap;
}
+.sequence-timeline {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.sequence-timeline__summary {
+ color: var(--color-muted);
+ font-size: 0.8rem;
+}
+
+.sequence-timeline__ruler {
+ position: relative;
+ height: 24px;
+ margin-left: 72px;
+}
+
+.sequence-timeline__hour {
+ position: absolute;
+ top: 0;
+ transform: translateX(-50%);
+ color: var(--color-muted);
+ font-size: 0.7rem;
+ white-space: nowrap;
+}
+
+.sequence-timeline__track {
+ position: relative;
+ margin-left: 72px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 16px;
+ background:
+ linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01)),
+ rgba(9, 13, 18, 0.9);
+ overflow: hidden;
+}
+
+.sequence-timeline__lane {
+ position: absolute;
+ left: 0;
+ right: 0;
+ height: 52px;
+ border-top: 1px solid rgba(255, 255, 255, 0.04);
+}
+
+.sequence-timeline__lane:first-child {
+ border-top: 0;
+}
+
+.sequence-timeline__lane-label {
+ position: absolute;
+ left: -68px;
+ top: 50%;
+ width: 56px;
+ transform: translateY(-50%);
+ color: var(--color-muted);
+ font-size: 0.72rem;
+ text-align: right;
+}
+
+.sequence-timeline__minor-grid,
+.sequence-timeline__major-grid {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 1px;
+ pointer-events: none;
+}
+
+.sequence-timeline__minor-grid {
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.sequence-timeline__major-grid {
+ background: rgba(255, 255, 255, 0.11);
+}
+
+.sequence-timeline__clip {
+ position: absolute;
+ min-width: 18px;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 10px;
+ border-radius: 12px;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ cursor: grab;
+ overflow: hidden;
+}
+
+.sequence-timeline__clip:active {
+ cursor: grabbing;
+}
+
+.sequence-timeline__clip--held {
+ background: linear-gradient(135deg, #f5b66f, #d17f4a);
+ color: #24150d;
+}
+
+.sequence-timeline__clip--impulse {
+ background: linear-gradient(135deg, #8ec7d9, #5d8aa1);
+ color: #0d1a22;
+}
+
+.sequence-timeline__clip--selected {
+ box-shadow: 0 0 0 2px rgba(255, 250, 240, 0.35);
+}
+
+.sequence-timeline__clip-title {
+ min-width: 0;
+ flex: 1 1 auto;
+ overflow: hidden;
+ font-size: 0.76rem;
+ font-weight: 700;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.sequence-timeline__clip-time {
+ font-size: 0.68rem;
+ opacity: 0.8;
+ white-space: nowrap;
+}
+
+.sequence-timeline__clip-handle {
+ position: absolute;
+ top: 4px;
+ bottom: 4px;
+ width: 8px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.45);
+ cursor: ew-resize;
+}
+
+.sequence-timeline__clip-handle--start {
+ left: 4px;
+}
+
+.sequence-timeline__clip-handle--end {
+ right: 4px;
+}
+
.schedule-days {
display: flex;
flex-wrap: wrap;