@odori/cli 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-RXLB2CXH.js → chunk-RHG23EWW.js} +457 -241
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +45 -5
- package/dist/index.js +3 -3
- package/dist/registry-snapshot-JEVXYGS2.js +4868 -0
- package/package.json +3 -3
- package/src/assets.ts +90 -0
- package/src/brand-file.ts +16 -4
- package/src/cli.ts +38 -13
- package/src/commands/add.ts +37 -1
- package/src/commands/dev.ts +32 -2
- package/src/commands/doctor.ts +47 -2
- package/src/commands/exportVideo.ts +6 -0
- package/src/commands/{still.ts → frame.ts} +23 -9
- package/src/commands/update.ts +58 -7
- package/src/discovery.ts +63 -2
- package/src/index.ts +1 -1
- package/src/jobs.ts +1 -1
- package/src/registry-snapshot.json +1529 -327
- package/src/registry-source.ts +37 -2
- package/src/render.ts +8 -1
- package/src/server.ts +7 -1
- package/studio/src/Studio.tsx +6 -22
- package/studio/src/components/ExportPanel.tsx +90 -6
- package/studio/src/components/Inspector.tsx +101 -1
- package/studio/src/components/Navigator.tsx +149 -0
- package/studio/src/components/Settings.tsx +109 -0
- package/studio/src/components/Transport.tsx +98 -55
- package/studio/src/components/ui.tsx +16 -1
- package/studio/src/lib/highlight.ts +85 -0
- package/studio/src/settings.ts +87 -0
- package/studio/src/studio.css +350 -8
- package/studio/src/views/BrandsView.tsx +18 -1
- package/studio/src/views/ComponentsView.tsx +191 -26
- package/studio/src/views/HomeView.tsx +7 -4
- package/studio/src/views/VideosView.tsx +33 -6
- package/studio/src/virtual.d.ts +4 -1
- package/dist/registry-snapshot-BDP6PVYB.js +0 -3559
package/studio/src/studio.css
CHANGED
|
@@ -153,6 +153,18 @@ body.render-mode {
|
|
|
153
153
|
background-clip: content-box;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/* Anything you click says so, once, rather than per control. */
|
|
157
|
+
button:not(:disabled),
|
|
158
|
+
[role="button"]:not([aria-disabled="true"]),
|
|
159
|
+
summary {
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
button:disabled,
|
|
164
|
+
[role="button"][aria-disabled="true"] {
|
|
165
|
+
cursor: not-allowed;
|
|
166
|
+
}
|
|
167
|
+
|
|
156
168
|
/* ---------- primitives ---------- */
|
|
157
169
|
|
|
158
170
|
.button {
|
|
@@ -294,6 +306,66 @@ a.button {
|
|
|
294
306
|
font-size: 11px;
|
|
295
307
|
}
|
|
296
308
|
|
|
309
|
+
/* ---------- settings ---------- */
|
|
310
|
+
|
|
311
|
+
.settings {
|
|
312
|
+
position: relative;
|
|
313
|
+
}
|
|
314
|
+
/* Right-aligned: the trigger is the last thing in the header, so a menu
|
|
315
|
+
growing rightward from its left edge would leave the window. */
|
|
316
|
+
.settings-menu {
|
|
317
|
+
left: auto;
|
|
318
|
+
right: 0;
|
|
319
|
+
}
|
|
320
|
+
.menu-heading {
|
|
321
|
+
color: var(--subtle);
|
|
322
|
+
font-size: 11px;
|
|
323
|
+
letter-spacing: 0.04em;
|
|
324
|
+
margin: 6px 0 2px;
|
|
325
|
+
padding: 0 9px;
|
|
326
|
+
text-transform: uppercase;
|
|
327
|
+
}
|
|
328
|
+
.menu-heading:first-child {
|
|
329
|
+
margin-top: 2px;
|
|
330
|
+
}
|
|
331
|
+
.menu-hint {
|
|
332
|
+
color: var(--subtle);
|
|
333
|
+
font-size: 11px;
|
|
334
|
+
}
|
|
335
|
+
/* A tick in a reserved column, so choosing an option never reflows the row
|
|
336
|
+
or nudges the one under the cursor. */
|
|
337
|
+
.menu-item {
|
|
338
|
+
display: grid;
|
|
339
|
+
grid-template-columns: 1fr 14px;
|
|
340
|
+
}
|
|
341
|
+
.menu-item > span:first-child {
|
|
342
|
+
grid-column: 1;
|
|
343
|
+
}
|
|
344
|
+
.menu-hint {
|
|
345
|
+
grid-column: 1;
|
|
346
|
+
}
|
|
347
|
+
.menu-item[data-selected="true"]::after {
|
|
348
|
+
align-self: center;
|
|
349
|
+
color: var(--foreground);
|
|
350
|
+
content: "✓";
|
|
351
|
+
font-size: 11px;
|
|
352
|
+
grid-column: 2;
|
|
353
|
+
grid-row: 1 / span 2;
|
|
354
|
+
justify-self: end;
|
|
355
|
+
}
|
|
356
|
+
/* A stalled bar stops looking like a working one. */
|
|
357
|
+
.progress[data-stalled="true"] > span {
|
|
358
|
+
opacity: 0.5;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.menu-note {
|
|
362
|
+
border-top: 1px solid var(--border);
|
|
363
|
+
color: var(--subtle);
|
|
364
|
+
font-size: 11px;
|
|
365
|
+
margin: 4px 0 0;
|
|
366
|
+
padding: 7px 9px 3px;
|
|
367
|
+
}
|
|
368
|
+
|
|
297
369
|
/* Matches the transport's icon buttons rather than the host select. */
|
|
298
370
|
.rate {
|
|
299
371
|
appearance: none;
|
|
@@ -485,19 +557,93 @@ select.input {
|
|
|
485
557
|
|
|
486
558
|
.main {
|
|
487
559
|
display: grid;
|
|
488
|
-
/* A detail is
|
|
489
|
-
column. The
|
|
490
|
-
|
|
491
|
-
|
|
560
|
+
/* A detail is a list, a stage, and an inspector; galleries and pages are one
|
|
561
|
+
column. The side columns size to their contents, so a panel that puts
|
|
562
|
+
itself away takes its column with it and the stage grows into the gap
|
|
563
|
+
without a rule per combination of what is open. The inspector's width is
|
|
564
|
+
a root variable so its drag handle can resize it without React re-laying
|
|
565
|
+
anything out. */
|
|
566
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
492
567
|
overflow: hidden;
|
|
493
568
|
}
|
|
494
569
|
.main[data-single="true"] {
|
|
495
570
|
grid-template-columns: 1fr;
|
|
496
571
|
}
|
|
497
572
|
|
|
573
|
+
/* ---------- navigator ---------- */
|
|
574
|
+
|
|
575
|
+
.navigator {
|
|
576
|
+
background: var(--panel);
|
|
577
|
+
border-right: 1px solid var(--border);
|
|
578
|
+
display: flex;
|
|
579
|
+
flex-direction: column;
|
|
580
|
+
overflow: hidden;
|
|
581
|
+
width: 232px;
|
|
582
|
+
}
|
|
583
|
+
.navigator-head {
|
|
584
|
+
align-items: center;
|
|
585
|
+
border-bottom: 1px solid var(--border);
|
|
586
|
+
display: flex;
|
|
587
|
+
gap: 6px;
|
|
588
|
+
padding: 10px;
|
|
589
|
+
}
|
|
590
|
+
.navigator-search {
|
|
591
|
+
height: 28px;
|
|
592
|
+
font-size: 12px;
|
|
593
|
+
min-width: 0;
|
|
594
|
+
}
|
|
595
|
+
.navigator-scroll {
|
|
596
|
+
overflow-y: auto;
|
|
597
|
+
padding: 10px;
|
|
598
|
+
scrollbar-gutter: stable;
|
|
599
|
+
}
|
|
600
|
+
.navigator-group {
|
|
601
|
+
color: var(--subtle);
|
|
602
|
+
font-size: 11px;
|
|
603
|
+
font-weight: 500;
|
|
604
|
+
letter-spacing: 0.04em;
|
|
605
|
+
margin: 10px 0 4px 2px;
|
|
606
|
+
text-transform: uppercase;
|
|
607
|
+
}
|
|
608
|
+
.navigator-scroll > div:first-child .navigator-group {
|
|
609
|
+
margin-top: 0;
|
|
610
|
+
}
|
|
611
|
+
.navigator-empty {
|
|
612
|
+
padding: 4px 2px;
|
|
613
|
+
}
|
|
614
|
+
.navigator-toggle,
|
|
615
|
+
.navigator-reopen {
|
|
616
|
+
align-items: center;
|
|
617
|
+
background: transparent;
|
|
618
|
+
border: 1px solid transparent;
|
|
619
|
+
border-radius: var(--radius-sm);
|
|
620
|
+
color: var(--muted);
|
|
621
|
+
cursor: pointer;
|
|
622
|
+
display: inline-flex;
|
|
623
|
+
flex: none;
|
|
624
|
+
height: 28px;
|
|
625
|
+
justify-content: center;
|
|
626
|
+
width: 28px;
|
|
627
|
+
}
|
|
628
|
+
.navigator-toggle:hover,
|
|
629
|
+
.navigator-reopen:hover {
|
|
630
|
+
background: var(--hover);
|
|
631
|
+
color: var(--foreground);
|
|
632
|
+
}
|
|
633
|
+
/* Away, the way back floats where the list was. */
|
|
634
|
+
.navigator-reopen {
|
|
635
|
+
background: var(--panel);
|
|
636
|
+
border-color: var(--border);
|
|
637
|
+
left: 10px;
|
|
638
|
+
position: fixed;
|
|
639
|
+
top: 58px;
|
|
640
|
+
z-index: 5;
|
|
641
|
+
}
|
|
642
|
+
|
|
498
643
|
.inspector {
|
|
499
644
|
background: var(--panel);
|
|
500
645
|
border-left: 1px solid var(--border);
|
|
646
|
+
width: clamp(240px, var(--inspector-width, 320px), 50vw);
|
|
501
647
|
container-type: inline-size;
|
|
502
648
|
display: flex;
|
|
503
649
|
overflow: hidden;
|
|
@@ -507,6 +653,11 @@ select.input {
|
|
|
507
653
|
flex: 1;
|
|
508
654
|
overflow-y: auto;
|
|
509
655
|
padding: 16px;
|
|
656
|
+
/* The gutter is reserved whether or not it is used. Code is taller than the
|
|
657
|
+
facts it replaces, so switching to it summoned a scrollbar, and the ten
|
|
658
|
+
pixels that took came out of the content: the header's controls jumped
|
|
659
|
+
sideways every time you changed face. */
|
|
660
|
+
scrollbar-gutter: stable;
|
|
510
661
|
}
|
|
511
662
|
|
|
512
663
|
|
|
@@ -526,6 +677,127 @@ select.input {
|
|
|
526
677
|
text-overflow: ellipsis;
|
|
527
678
|
white-space: nowrap;
|
|
528
679
|
}
|
|
680
|
+
/* Two views of one selection, not two modes: the segmented control sits in
|
|
681
|
+
the pane's own header rather than above the whole app. */
|
|
682
|
+
.inspector-views {
|
|
683
|
+
background: var(--hover);
|
|
684
|
+
border-radius: var(--radius-sm);
|
|
685
|
+
display: flex;
|
|
686
|
+
flex: none;
|
|
687
|
+
gap: 2px;
|
|
688
|
+
margin-left: auto;
|
|
689
|
+
padding: 2px;
|
|
690
|
+
}
|
|
691
|
+
.inspector-views button {
|
|
692
|
+
background: transparent;
|
|
693
|
+
border: 0;
|
|
694
|
+
border-radius: calc(var(--radius-sm) - 1px);
|
|
695
|
+
color: var(--muted);
|
|
696
|
+
cursor: pointer;
|
|
697
|
+
font: inherit;
|
|
698
|
+
font-size: 11px;
|
|
699
|
+
padding: 3px 9px;
|
|
700
|
+
}
|
|
701
|
+
.inspector-views button:hover {
|
|
702
|
+
color: var(--foreground);
|
|
703
|
+
}
|
|
704
|
+
/* A lift off the track it sits in, and nothing else. The weight and the
|
|
705
|
+
border this used to add both changed the button's size, so selecting one
|
|
706
|
+
nudged the other sideways: emphasis has to come from colour alone, or the
|
|
707
|
+
control moves while you read it. */
|
|
708
|
+
.inspector-views button[data-active="true"] {
|
|
709
|
+
background: color-mix(in srgb, var(--foreground) 10%, var(--hover));
|
|
710
|
+
color: var(--foreground);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/* Which of a component's files is being read. */
|
|
714
|
+
.source-files {
|
|
715
|
+
display: flex;
|
|
716
|
+
flex-wrap: wrap;
|
|
717
|
+
gap: 6px;
|
|
718
|
+
margin-bottom: 10px;
|
|
719
|
+
}
|
|
720
|
+
.source-files button {
|
|
721
|
+
background: transparent;
|
|
722
|
+
border: 1px solid var(--border);
|
|
723
|
+
border-radius: 999px;
|
|
724
|
+
color: var(--muted);
|
|
725
|
+
font-family: var(--font-mono);
|
|
726
|
+
font-size: 11px;
|
|
727
|
+
padding: 3px 10px;
|
|
728
|
+
}
|
|
729
|
+
.source-files button:hover {
|
|
730
|
+
color: var(--foreground);
|
|
731
|
+
}
|
|
732
|
+
.source-files button[data-active="true"] {
|
|
733
|
+
background: var(--hover);
|
|
734
|
+
border-color: var(--border-strong);
|
|
735
|
+
color: var(--foreground);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/*
|
|
739
|
+
* Token colours, taken from the same GitHub themes the docs site highlights
|
|
740
|
+
* with, so a component's source reads the same in both places.
|
|
741
|
+
*/
|
|
742
|
+
:root {
|
|
743
|
+
--token-comment: #6e7781;
|
|
744
|
+
--token-string: #0a3069;
|
|
745
|
+
--token-keyword: #cf222e;
|
|
746
|
+
--token-number: #0550ae;
|
|
747
|
+
--token-tag: #116329;
|
|
748
|
+
--token-attr: #953800;
|
|
749
|
+
--token-fn: #8250df;
|
|
750
|
+
}
|
|
751
|
+
:root[data-theme="dark"] {
|
|
752
|
+
--token-comment: #8b949e;
|
|
753
|
+
--token-string: #a5d6ff;
|
|
754
|
+
--token-keyword: #ff7b72;
|
|
755
|
+
--token-number: #79c0ff;
|
|
756
|
+
--token-tag: #7ee787;
|
|
757
|
+
--token-attr: #ffa657;
|
|
758
|
+
--token-fn: #d2a8ff;
|
|
759
|
+
}
|
|
760
|
+
@media (prefers-color-scheme: dark) {
|
|
761
|
+
:root:not([data-theme="light"]) {
|
|
762
|
+
--token-comment: #8b949e;
|
|
763
|
+
--token-string: #a5d6ff;
|
|
764
|
+
--token-keyword: #ff7b72;
|
|
765
|
+
--token-number: #79c0ff;
|
|
766
|
+
--token-tag: #7ee787;
|
|
767
|
+
--token-attr: #ffa657;
|
|
768
|
+
--token-fn: #d2a8ff;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.source [data-token="comment"] { color: var(--token-comment); font-style: italic; }
|
|
773
|
+
.source [data-token="string"] { color: var(--token-string); }
|
|
774
|
+
.source [data-token="keyword"] { color: var(--token-keyword); }
|
|
775
|
+
.source [data-token="number"] { color: var(--token-number); }
|
|
776
|
+
.source [data-token="tag"] { color: var(--token-tag); }
|
|
777
|
+
.source [data-token="attr"] { color: var(--token-attr); }
|
|
778
|
+
.source [data-token="fn"] { color: var(--token-fn); }
|
|
779
|
+
.source [data-token="punct"] { color: var(--muted); }
|
|
780
|
+
|
|
781
|
+
/*
|
|
782
|
+
* Code scrolls the full width of the pane. The inspector pads its content by
|
|
783
|
+
* sixteen, which is right for prose and wrong for a scrolling region: it left
|
|
784
|
+
* a gutter the code could not reach, so a long line appeared to stop short of
|
|
785
|
+
* an edge it had not reached. The negative margin gives the scroller the whole
|
|
786
|
+
* pane and the padding puts the text back where it was.
|
|
787
|
+
*/
|
|
788
|
+
.source {
|
|
789
|
+
color: var(--foreground);
|
|
790
|
+
font-family: var(--font-mono);
|
|
791
|
+
font-size: 11px;
|
|
792
|
+
line-height: 1.65;
|
|
793
|
+
margin: 0 -16px;
|
|
794
|
+
overflow-x: auto;
|
|
795
|
+
overscroll-behavior-x: contain;
|
|
796
|
+
padding: 0 16px;
|
|
797
|
+
tab-size: 2;
|
|
798
|
+
white-space: pre;
|
|
799
|
+
}
|
|
800
|
+
|
|
529
801
|
.inspector-toggle,
|
|
530
802
|
.inspector-reopen {
|
|
531
803
|
align-items: center;
|
|
@@ -556,9 +828,7 @@ select.input {
|
|
|
556
828
|
}
|
|
557
829
|
|
|
558
830
|
/* A collapsed inspector hands its column to the stage. */
|
|
559
|
-
|
|
560
|
-
grid-template-columns: minmax(0, 1fr);
|
|
561
|
-
}
|
|
831
|
+
/* Panels carry their own width now, so collapsing one needs no grid rule. */
|
|
562
832
|
.inspector-resize {
|
|
563
833
|
cursor: col-resize;
|
|
564
834
|
/* Fully inside the pane: the inspector clips overflow, and a handle hung
|
|
@@ -630,6 +900,33 @@ html[data-inspector="collapsed"] .main:not([data-single="true"]) {
|
|
|
630
900
|
}
|
|
631
901
|
}
|
|
632
902
|
|
|
903
|
+
/* Filter the catalog by the video a component is actually used in. */
|
|
904
|
+
.catalog-filter {
|
|
905
|
+
display: flex;
|
|
906
|
+
flex-wrap: wrap;
|
|
907
|
+
gap: 6px;
|
|
908
|
+
padding: 16px 20px 0;
|
|
909
|
+
}
|
|
910
|
+
.catalog-filter button {
|
|
911
|
+
background: transparent;
|
|
912
|
+
border: 1px solid var(--border);
|
|
913
|
+
border-radius: 999px;
|
|
914
|
+
color: var(--muted);
|
|
915
|
+
cursor: pointer;
|
|
916
|
+
font: inherit;
|
|
917
|
+
font-size: 12px;
|
|
918
|
+
padding: 4px 12px;
|
|
919
|
+
}
|
|
920
|
+
.catalog-filter button:hover {
|
|
921
|
+
border-color: var(--border-strong);
|
|
922
|
+
color: var(--foreground);
|
|
923
|
+
}
|
|
924
|
+
.catalog-filter button[data-active="true"] {
|
|
925
|
+
background: var(--hover);
|
|
926
|
+
border-color: var(--border-strong);
|
|
927
|
+
color: var(--foreground);
|
|
928
|
+
}
|
|
929
|
+
|
|
633
930
|
/* A gallery page or an explainer page: one scrolling column with a readable
|
|
634
931
|
lead-in. */
|
|
635
932
|
.page {
|
|
@@ -714,6 +1011,13 @@ html[data-inspector="collapsed"] .main:not([data-single="true"]) {
|
|
|
714
1011
|
|
|
715
1012
|
.stage {
|
|
716
1013
|
display: grid;
|
|
1014
|
+
/* The column has to be named. An implicit grid column is auto, which means
|
|
1015
|
+
max-content, so the transport's row of controls set the width and the
|
|
1016
|
+
timeline was laid out at its natural size and then clipped: in a pane this
|
|
1017
|
+
narrow the right half of every video sat under the inspector, unclickable,
|
|
1018
|
+
and the playhead walked off the edge on its way there. min-width on .stage
|
|
1019
|
+
does not help, because the overflow is the track inside it. */
|
|
1020
|
+
grid-template-columns: minmax(0, 1fr);
|
|
717
1021
|
grid-template-rows: minmax(0, 1fr) auto;
|
|
718
1022
|
min-width: 0;
|
|
719
1023
|
overflow: hidden;
|
|
@@ -756,13 +1060,19 @@ html[data-inspector="collapsed"] .main:not([data-single="true"]) {
|
|
|
756
1060
|
border-top: 1px solid var(--border);
|
|
757
1061
|
display: grid;
|
|
758
1062
|
gap: 10px;
|
|
1063
|
+
min-width: 0;
|
|
759
1064
|
padding: 10px 14px 14px;
|
|
760
1065
|
}
|
|
761
1066
|
|
|
762
1067
|
.transport-row {
|
|
763
1068
|
align-items: center;
|
|
764
1069
|
display: flex;
|
|
1070
|
+
/* Wrapping is what lets the transport be narrower than its controls. Without
|
|
1071
|
+
it the row is a single min-content block and it is the row, not the
|
|
1072
|
+
timeline, that decides how wide the stage has to be. */
|
|
1073
|
+
flex-wrap: wrap;
|
|
765
1074
|
gap: 8px;
|
|
1075
|
+
min-width: 0;
|
|
766
1076
|
}
|
|
767
1077
|
|
|
768
1078
|
.timecode {
|
|
@@ -777,8 +1087,13 @@ html[data-inspector="collapsed"] .main:not([data-single="true"]) {
|
|
|
777
1087
|
}
|
|
778
1088
|
|
|
779
1089
|
.timeline {
|
|
780
|
-
|
|
1090
|
+
/* Only the track is clickable, so only the track shows a pointer. */
|
|
781
1091
|
display: grid;
|
|
1092
|
+
/* Named for the same reason .stage is: an implicit auto column would take
|
|
1093
|
+
its width from the ruler and the scene labels rather than from the space
|
|
1094
|
+
available, and push the track out from under the cursor. */
|
|
1095
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1096
|
+
min-width: 0;
|
|
782
1097
|
gap: 4px;
|
|
783
1098
|
position: relative;
|
|
784
1099
|
user-select: none;
|
|
@@ -792,6 +1107,8 @@ html[data-inspector="collapsed"] .main:not([data-single="true"]) {
|
|
|
792
1107
|
justify-content: space-between;
|
|
793
1108
|
}
|
|
794
1109
|
.track {
|
|
1110
|
+
/* The track is a click target, not something to drag. */
|
|
1111
|
+
cursor: pointer;
|
|
795
1112
|
background: var(--field);
|
|
796
1113
|
border: 1px solid var(--border);
|
|
797
1114
|
border-radius: var(--radius-sm);
|
|
@@ -903,6 +1220,21 @@ html[data-inspector="collapsed"] .main:not([data-single="true"]) {
|
|
|
903
1220
|
width: 7.5px;
|
|
904
1221
|
}
|
|
905
1222
|
|
|
1223
|
+
/* The frame under the cursor, which is a suggestion until it is clicked. It
|
|
1224
|
+
reads as the same instrument as the playhead, turned down. */
|
|
1225
|
+
.playhead[data-preview="true"],
|
|
1226
|
+
.playhead[data-preview="true"]::before {
|
|
1227
|
+
background: var(--subtle);
|
|
1228
|
+
}
|
|
1229
|
+
.playhead[data-preview="true"] {
|
|
1230
|
+
opacity: 0.75;
|
|
1231
|
+
}
|
|
1232
|
+
/* Muted rather than moved: the readout must not change width when it starts
|
|
1233
|
+
reporting the hovered frame, or the whole row shifts under the cursor. */
|
|
1234
|
+
.timecode[data-preview="true"] {
|
|
1235
|
+
color: var(--subtle);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
906
1238
|
/* ---------- inspector ---------- */
|
|
907
1239
|
|
|
908
1240
|
.facts {
|
|
@@ -1263,6 +1595,16 @@ html[data-inspector="collapsed"] .main:not([data-single="true"]) {
|
|
|
1263
1595
|
color: var(--foreground);
|
|
1264
1596
|
}
|
|
1265
1597
|
|
|
1598
|
+
/* A drawer inside a family. Quieter than the family it sits under, and
|
|
1599
|
+
indented enough that the nesting reads without a rule to draw it. */
|
|
1600
|
+
.group-title {
|
|
1601
|
+
color: var(--muted);
|
|
1602
|
+
font-size: 12px;
|
|
1603
|
+
font-weight: 500;
|
|
1604
|
+
letter-spacing: -0.01em;
|
|
1605
|
+
margin: 2px 0 8px;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1266
1608
|
/* ---------- home ---------- */
|
|
1267
1609
|
|
|
1268
1610
|
.home {
|
|
@@ -13,6 +13,7 @@ import {CanvasStage} from "../components/CanvasStage";
|
|
|
13
13
|
import {Transport} from "../components/Transport";
|
|
14
14
|
import {Empty, Fact, SectionTitle, Separator} from "../components/ui";
|
|
15
15
|
import {Inspector} from "../components/Inspector";
|
|
16
|
+
import {Navigator} from "../components/Navigator";
|
|
16
17
|
import {useShortcuts} from "../shortcuts";
|
|
17
18
|
|
|
18
19
|
/** Brands come from videos/**\/brands modules and from every resolved layout. */
|
|
@@ -101,9 +102,21 @@ export const BrandsView = ({
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
const layout = defineVideoLayout({extends: base, brand});
|
|
105
|
+
const brandFile = project.files.brands.find((item) => item.id === brand.name)?.file;
|
|
104
106
|
|
|
105
107
|
return (
|
|
106
108
|
<>
|
|
109
|
+
<Navigator
|
|
110
|
+
label="Brands"
|
|
111
|
+
selected={brand.name}
|
|
112
|
+
onSelect={onSelect}
|
|
113
|
+
items={brands.map((item) => ({
|
|
114
|
+
id: item.name,
|
|
115
|
+
title: item.name,
|
|
116
|
+
detail: `${Object.keys(item.audio.cues).length} cues · ${item.audio.targetLufs} LUFS`,
|
|
117
|
+
}))}
|
|
118
|
+
/>
|
|
119
|
+
|
|
107
120
|
<section className="stage">
|
|
108
121
|
<CanvasStage width={layout.format.width} height={layout.format.height}>
|
|
109
122
|
<OdoriRuntime
|
|
@@ -125,7 +138,11 @@ export const BrandsView = ({
|
|
|
125
138
|
/>
|
|
126
139
|
</section>
|
|
127
140
|
|
|
128
|
-
<Inspector
|
|
141
|
+
<Inspector
|
|
142
|
+
title={brand.name}
|
|
143
|
+
hint={brandFile}
|
|
144
|
+
source={brandFile}
|
|
145
|
+
>
|
|
129
146
|
<SectionTitle>Preview with</SectionTitle>
|
|
130
147
|
<ul className="list">
|
|
131
148
|
{videos.map((video) => (
|