moontide-mcp 2.1.0 → 2.4.0
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/README.md +54 -331
- package/dist/ask.js +59 -12
- package/dist/catalog.js +118 -9
- package/dist/instruments.js +83 -9
- package/dist/mcp.js +146 -20
- package/dist/midi-state.js +43 -1
- package/dist/protocol.js +123 -5
- package/dist/reference.js +12 -0
- package/dist/request-status.js +221 -0
- package/dist/session.js +203 -7
- package/dist/vocabulary.js +420 -0
- package/package.json +3 -2
- package/page/ask-view.js +118 -50
- package/page/ask.js +139 -169
- package/page/bar-model.js +44 -2
- package/page/change-timing.js +35 -0
- package/page/index.html +6 -2
- package/page/instruments-model.js +50 -3
- package/page/instruments-view.js +14 -2
- package/page/jam-analysis.js +68 -23
- package/page/jam-model.js +185 -29
- package/page/jam-resolve.js +271 -0
- package/page/jam-view.js +165 -10
- package/page/lights.js +29 -7
- package/page/main.js +807 -66
- package/page/midi-clock.js +22 -3
- package/page/midi-in.js +407 -0
- package/page/midi-out.js +557 -48
- package/page/midi-plan.js +42 -6
- package/page/midi-rig.js +286 -13
- package/page/midi-settings.js +507 -0
- package/page/mixer-view.js +7 -1
- package/page/note-panel.js +29 -8
- package/page/note-tag.js +19 -4
- package/page/page-rewrite.js +1 -1
- package/page/played-buffer.js +208 -0
- package/page/query-check.js +21 -2
- package/page/request-status-view.js +419 -0
- package/page/request-status.js +686 -0
- package/page/scratch-gesture.js +239 -0
- package/page/scratch-listen.js +260 -0
- package/page/scratch-model.js +1696 -0
- package/page/scratch-view.js +2176 -0
- package/page/song-grid-model.js +15 -0
- package/page/song-grid-view.js +1 -1
- package/page/structure.js +2 -1
- package/page/style.css +1243 -43
- package/page/tempo-glide.js +330 -0
- package/page/tempo-guard.js +62 -10
- package/page/timeline-edit-view.js +1 -1
- package/page/timeline.js +2 -1
- package/page/top-bar-view.js +18 -13
- package/page/views.js +20 -7
- package/reference/midi.md +2 -0
- package/reference/motif.md +83 -0
- package/reference/settings.md +161 -0
- package/reference/transitions.md +13 -1
package/page/style.css
CHANGED
|
@@ -456,34 +456,6 @@ body {
|
|
|
456
456
|
font-size: 0.8125rem;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
-
/* The status of the latest request to Claude. No display rule: #top-bar > span styles it and the browser's [hidden]
|
|
460
|
-
works unaided, so nothing to say takes no width. It sits before the Session button in the markup, so it is right of
|
|
461
|
-
the Layout group. It holds the long sentence and a short form: the short one is for fit step 3 and is aria-hidden. */
|
|
462
|
-
#ask-status {
|
|
463
|
-
flex: none;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
#ask-status .ask-short {
|
|
467
|
-
display: none;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/* Fit step 3 and 4: the short form shows, and the long sentence is visually hidden but kept in the accessibility tree
|
|
471
|
-
(never display: none), the recipe of #change-status. */
|
|
472
|
-
#top-bar[data-fit="3"] #ask-status .ask-short,
|
|
473
|
-
#top-bar[data-fit="4"] #ask-status .ask-short {
|
|
474
|
-
display: inline;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
#top-bar[data-fit="3"] #ask-status .ask-long,
|
|
478
|
-
#top-bar[data-fit="4"] #ask-status .ask-long {
|
|
479
|
-
position: absolute;
|
|
480
|
-
width: 1px;
|
|
481
|
-
height: 1px;
|
|
482
|
-
overflow: hidden;
|
|
483
|
-
clip-path: inset(50%);
|
|
484
|
-
white-space: nowrap;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
459
|
/* The cycle and beat beside the tempo, quiet decoration (Label size, mono, muted). The hidden attribute
|
|
488
460
|
hides it while nothing plays, so it has no display rule. Fit step 1 drops it with the wordmark (.bar-deco). */
|
|
489
461
|
#top-bar #cycle-beat {
|
|
@@ -494,9 +466,7 @@ body {
|
|
|
494
466
|
}
|
|
495
467
|
|
|
496
468
|
/* Whether page requests reach Claude live. No display rule: #top-bar > span
|
|
497
|
-
styles it and the browser's [hidden] works unaided.
|
|
498
|
-
so it sits right of the request status and left of the motion switch, and the request status
|
|
499
|
-
appearing never moves it. */
|
|
469
|
+
styles it and the browser's [hidden] works unaided. */
|
|
500
470
|
#page-requests {
|
|
501
471
|
flex-shrink: 0;
|
|
502
472
|
}
|
|
@@ -1015,6 +985,300 @@ body {
|
|
|
1015
985
|
font-weight: 400;
|
|
1016
986
|
}
|
|
1017
987
|
|
|
988
|
+
/* The Requests chip, before the Session button: the same box as every control of the bar (1.75rem high, 0.75rem at the
|
|
989
|
+
sides), so it lines up with its neighbour. It is hidden while no request was sent, and the count in its pill is the
|
|
990
|
+
requests still in flight. Neutral colors only: the accent means live now and red belongs to the failed-change banner.
|
|
991
|
+
No transition and no animation: everything changes in one frame. */
|
|
992
|
+
#requests-toggle {
|
|
993
|
+
flex: none;
|
|
994
|
+
display: inline-flex;
|
|
995
|
+
align-items: center;
|
|
996
|
+
gap: 0.25rem;
|
|
997
|
+
box-sizing: border-box;
|
|
998
|
+
min-width: 1.75rem;
|
|
999
|
+
min-height: 1.75rem;
|
|
1000
|
+
padding: 0 0.75rem;
|
|
1001
|
+
background: transparent;
|
|
1002
|
+
border: 1px solid transparent;
|
|
1003
|
+
border-radius: 0.5rem;
|
|
1004
|
+
color: var(--text-muted);
|
|
1005
|
+
font-family: inherit;
|
|
1006
|
+
font-size: 0.9375rem;
|
|
1007
|
+
font-weight: 400;
|
|
1008
|
+
line-height: 1.5;
|
|
1009
|
+
white-space: nowrap;
|
|
1010
|
+
cursor: pointer;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
#requests-toggle[hidden] {
|
|
1014
|
+
display: none;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
#requests-toggle[data-inflight="true"] {
|
|
1018
|
+
color: var(--text);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/* The word is the text color on hover, never muted on the fill. */
|
|
1022
|
+
#requests-toggle:hover {
|
|
1023
|
+
background: var(--surface-2);
|
|
1024
|
+
color: var(--text);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
#requests-toggle[aria-expanded="true"] {
|
|
1028
|
+
color: var(--text);
|
|
1029
|
+
border-color: var(--border);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
#requests-toggle:focus-visible {
|
|
1033
|
+
outline: 1px solid var(--text-muted);
|
|
1034
|
+
outline-offset: 1px;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
.rq-count {
|
|
1038
|
+
background: var(--surface-2);
|
|
1039
|
+
color: var(--text);
|
|
1040
|
+
border-radius: 1rem;
|
|
1041
|
+
padding: 0 0.5rem;
|
|
1042
|
+
font-family: var(--font-mono);
|
|
1043
|
+
font-size: 0.8125rem;
|
|
1044
|
+
font-weight: 400;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
#requests-toggle[data-inflight="true"] .rq-count {
|
|
1048
|
+
font-weight: 600;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
.rq-count[hidden] {
|
|
1052
|
+
display: none;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
#top-bar[data-fit="4"] #requests-toggle {
|
|
1056
|
+
padding: 0 0.5rem;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/* The panel under the bar's right end: an overlay that pushes nothing, under the Ask box and the popovers (1000) and
|
|
1060
|
+
the return banner (900), level with the Session drawer (800), which it never shares the screen with. One row per
|
|
1061
|
+
request, newest first; it scrolls inside itself. */
|
|
1062
|
+
#requests-panel {
|
|
1063
|
+
position: absolute;
|
|
1064
|
+
top: 100%;
|
|
1065
|
+
right: 1rem;
|
|
1066
|
+
margin-top: 0.5rem;
|
|
1067
|
+
z-index: 800;
|
|
1068
|
+
box-sizing: border-box;
|
|
1069
|
+
width: 26rem;
|
|
1070
|
+
max-width: calc(100vw - 2rem);
|
|
1071
|
+
max-height: 20rem;
|
|
1072
|
+
overflow-y: auto;
|
|
1073
|
+
padding: 0.5rem;
|
|
1074
|
+
display: flex;
|
|
1075
|
+
flex-direction: column;
|
|
1076
|
+
gap: 0.25rem;
|
|
1077
|
+
background: var(--secondary);
|
|
1078
|
+
border: 1px solid var(--text-muted);
|
|
1079
|
+
box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.45);
|
|
1080
|
+
font-size: 0.8125rem;
|
|
1081
|
+
line-height: 1.5;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
#requests-panel[hidden] {
|
|
1085
|
+
display: none;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
#requests-panel:focus {
|
|
1089
|
+
outline: none;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
#requests-panel:focus-visible {
|
|
1093
|
+
outline: 1px solid var(--text-muted);
|
|
1094
|
+
outline-offset: -1px;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.rq-list,
|
|
1098
|
+
.rq-recent-body {
|
|
1099
|
+
margin: 0;
|
|
1100
|
+
padding: 0;
|
|
1101
|
+
list-style: none;
|
|
1102
|
+
display: flex;
|
|
1103
|
+
flex-direction: column;
|
|
1104
|
+
gap: 0.25rem;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.rq-recent-body[hidden] {
|
|
1108
|
+
display: none;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
.rq-recent[hidden] {
|
|
1112
|
+
display: none;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/* A row is two lines: the start of the request and its time, then a shape and the words of the state. The hover and
|
|
1116
|
+
the open state change the border, never the fill, so muted text is never placed on the lighter surface. */
|
|
1117
|
+
.rq-row {
|
|
1118
|
+
display: grid;
|
|
1119
|
+
grid-template-columns: 0.5rem 1fr auto;
|
|
1120
|
+
column-gap: 0.5rem;
|
|
1121
|
+
align-items: center;
|
|
1122
|
+
width: 100%;
|
|
1123
|
+
min-width: 1.75rem;
|
|
1124
|
+
min-height: 3rem;
|
|
1125
|
+
box-sizing: border-box;
|
|
1126
|
+
padding: 0.5rem 1rem;
|
|
1127
|
+
background: transparent;
|
|
1128
|
+
border: 1px solid transparent;
|
|
1129
|
+
color: var(--text);
|
|
1130
|
+
font-family: inherit;
|
|
1131
|
+
font-size: 0.8125rem;
|
|
1132
|
+
font-weight: 400;
|
|
1133
|
+
line-height: 1.5;
|
|
1134
|
+
text-align: left;
|
|
1135
|
+
cursor: pointer;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
.rq-row:hover {
|
|
1139
|
+
border-color: var(--border);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.rq-row[aria-expanded="true"] {
|
|
1143
|
+
border-color: var(--border);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
.rq-row:focus-visible,
|
|
1147
|
+
.rq-recent-head:focus-visible {
|
|
1148
|
+
outline: 1px solid var(--text-muted);
|
|
1149
|
+
outline-offset: -1px;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
.rq-text {
|
|
1153
|
+
grid-column: 1 / 3;
|
|
1154
|
+
min-width: 0;
|
|
1155
|
+
overflow: hidden;
|
|
1156
|
+
text-overflow: ellipsis;
|
|
1157
|
+
white-space: nowrap;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
.rq-time {
|
|
1161
|
+
grid-column: 3;
|
|
1162
|
+
font-family: var(--font-mono);
|
|
1163
|
+
color: var(--text-muted);
|
|
1164
|
+
white-space: nowrap;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
/* The shape backs the words up and nothing more: a ring while it goes on, a disc when it is done, a dashed ring when
|
|
1168
|
+
it ended without a change, a square when it did not work. Text colors only. */
|
|
1169
|
+
.rq-mark {
|
|
1170
|
+
grid-column: 1;
|
|
1171
|
+
grid-row: 2;
|
|
1172
|
+
width: 0.5rem;
|
|
1173
|
+
height: 0.5rem;
|
|
1174
|
+
box-sizing: border-box;
|
|
1175
|
+
border-radius: 50%;
|
|
1176
|
+
border: 1px solid var(--text-muted);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.rq-mark[data-shape="done"] {
|
|
1180
|
+
background: var(--text);
|
|
1181
|
+
border-color: var(--text);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.rq-mark[data-shape="ended"] {
|
|
1185
|
+
border-style: dashed;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
.rq-mark[data-shape="bad"] {
|
|
1189
|
+
background: var(--text);
|
|
1190
|
+
border-color: var(--text);
|
|
1191
|
+
border-radius: 0;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
.rq-state {
|
|
1195
|
+
grid-column: 2 / 4;
|
|
1196
|
+
grid-row: 2;
|
|
1197
|
+
min-width: 0;
|
|
1198
|
+
overflow: hidden;
|
|
1199
|
+
text-overflow: ellipsis;
|
|
1200
|
+
white-space: nowrap;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.rq-item[data-finished="true"] .rq-text {
|
|
1204
|
+
color: var(--text-muted);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
.rq-detail {
|
|
1208
|
+
box-sizing: border-box;
|
|
1209
|
+
margin: 0.25rem 0 0;
|
|
1210
|
+
padding: 0.5rem;
|
|
1211
|
+
max-height: 8rem;
|
|
1212
|
+
overflow-y: auto;
|
|
1213
|
+
background: var(--dominant);
|
|
1214
|
+
border: 1px solid var(--border);
|
|
1215
|
+
overflow-wrap: anywhere;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.rq-detail[hidden] {
|
|
1219
|
+
display: none;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
.rq-detail p {
|
|
1223
|
+
margin: 0 0 0.5rem;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
.rq-detail p:last-child {
|
|
1227
|
+
margin-bottom: 0;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.rq-detail .rq-about,
|
|
1231
|
+
.rq-detail .rq-basis {
|
|
1232
|
+
color: var(--text-muted);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.rq-detail .rq-reason {
|
|
1236
|
+
font-family: var(--font-mono);
|
|
1237
|
+
white-space: pre-wrap;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
.rq-recent-head {
|
|
1241
|
+
display: flex;
|
|
1242
|
+
align-items: center;
|
|
1243
|
+
gap: 0.5rem;
|
|
1244
|
+
width: 100%;
|
|
1245
|
+
min-width: 2rem;
|
|
1246
|
+
min-height: 2rem;
|
|
1247
|
+
box-sizing: border-box;
|
|
1248
|
+
padding: 0 1rem;
|
|
1249
|
+
background: transparent;
|
|
1250
|
+
border: 1px solid transparent;
|
|
1251
|
+
color: var(--text-muted);
|
|
1252
|
+
font-family: inherit;
|
|
1253
|
+
font-size: 0.8125rem;
|
|
1254
|
+
font-weight: 400;
|
|
1255
|
+
line-height: 1.5;
|
|
1256
|
+
text-align: left;
|
|
1257
|
+
cursor: pointer;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.rq-recent-head:hover {
|
|
1261
|
+
color: var(--text);
|
|
1262
|
+
border-color: var(--border);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.rq-recent .rq-item .rq-state {
|
|
1266
|
+
color: var(--text-muted);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/* The spoken line of the Requests list: visually hidden, kept in the accessibility tree, always present and never
|
|
1270
|
+
[hidden] (the recipe of #change-status). */
|
|
1271
|
+
#requests-live {
|
|
1272
|
+
position: absolute;
|
|
1273
|
+
width: 1px;
|
|
1274
|
+
height: 1px;
|
|
1275
|
+
overflow: hidden;
|
|
1276
|
+
clip-path: inset(50%);
|
|
1277
|
+
white-space: nowrap;
|
|
1278
|
+
font-size: 0.8125rem;
|
|
1279
|
+
line-height: 1.5;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1018
1282
|
/* The strip: one muted line under the bar, only while the drawer is closed and a condition holds. */
|
|
1019
1283
|
#session-strip {
|
|
1020
1284
|
display: flex;
|
|
@@ -4533,19 +4797,113 @@ button.mx-name:focus-visible {
|
|
|
4533
4797
|
text-overflow: ellipsis;
|
|
4534
4798
|
}
|
|
4535
4799
|
|
|
4536
|
-
/* The
|
|
4537
|
-
|
|
4538
|
-
#jam-
|
|
4539
|
-
display: flex;
|
|
4540
|
-
flex-direction: row;
|
|
4541
|
-
flex-wrap: wrap;
|
|
4542
|
-
align-items: baseline;
|
|
4543
|
-
column-gap: 0.5rem;
|
|
4800
|
+
/* The line that says why a scale is in use when several fit: a bold lead word and one plain sentence under the scale
|
|
4801
|
+
name. It is no warning, so it wears the text colour only, and it is hidden for a scale that was simply heard. */
|
|
4802
|
+
#jam-reason {
|
|
4544
4803
|
margin: 0;
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4804
|
+
font-size: 1rem;
|
|
4805
|
+
font-weight: 400;
|
|
4806
|
+
line-height: 1.5;
|
|
4807
|
+
color: var(--text);
|
|
4808
|
+
overflow-wrap: anywhere;
|
|
4809
|
+
}
|
|
4810
|
+
|
|
4811
|
+
#jam-reason[hidden] {
|
|
4812
|
+
display: none;
|
|
4813
|
+
}
|
|
4814
|
+
|
|
4815
|
+
#jam-reason-lead {
|
|
4816
|
+
font-weight: 600;
|
|
4817
|
+
}
|
|
4818
|
+
|
|
4819
|
+
/* The scales that fit, under the reason line: a quiet caption, then one item per scale. While one can be chosen each item is a
|
|
4820
|
+
button and the one in use is pressed (a lighter fill and an underline, no weight change, so nothing shifts); when none can
|
|
4821
|
+
be chosen the items are plain text with no hover and no pointer. Never cut: an item keeps its whole name and the row wraps
|
|
4822
|
+
between items. Text colours only, no Jam hue: a list of scales is no warning. */
|
|
4823
|
+
#jam-candidates {
|
|
4824
|
+
display: flex;
|
|
4825
|
+
flex-direction: column;
|
|
4826
|
+
gap: 0.25rem;
|
|
4827
|
+
}
|
|
4828
|
+
|
|
4829
|
+
#jam-candidates[hidden] {
|
|
4830
|
+
display: none;
|
|
4831
|
+
}
|
|
4832
|
+
|
|
4833
|
+
#jam-candidates-label,
|
|
4834
|
+
#jam-candidates-more {
|
|
4835
|
+
font-size: 0.8125rem;
|
|
4836
|
+
font-weight: 400;
|
|
4837
|
+
line-height: 1.5;
|
|
4838
|
+
color: var(--text-muted);
|
|
4839
|
+
}
|
|
4840
|
+
|
|
4841
|
+
#jam-candidates-more[hidden] {
|
|
4842
|
+
display: none;
|
|
4843
|
+
}
|
|
4844
|
+
|
|
4845
|
+
#jam-candidate-list {
|
|
4846
|
+
display: flex;
|
|
4847
|
+
flex-direction: row;
|
|
4848
|
+
flex-wrap: wrap;
|
|
4849
|
+
gap: 0.5rem;
|
|
4850
|
+
}
|
|
4851
|
+
|
|
4852
|
+
.jam-candidate {
|
|
4853
|
+
box-sizing: border-box;
|
|
4854
|
+
min-height: 1.75rem;
|
|
4855
|
+
padding: 0 0.5rem;
|
|
4856
|
+
display: inline-flex;
|
|
4857
|
+
align-items: center;
|
|
4858
|
+
border: 1px solid var(--border);
|
|
4859
|
+
border-radius: 0;
|
|
4860
|
+
background: transparent;
|
|
4861
|
+
color: var(--text-muted);
|
|
4862
|
+
font: inherit;
|
|
4863
|
+
font-size: 0.8125rem;
|
|
4864
|
+
font-weight: 400;
|
|
4865
|
+
line-height: 1.5;
|
|
4866
|
+
white-space: nowrap;
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
button.jam-candidate {
|
|
4870
|
+
cursor: pointer;
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4873
|
+
button.jam-candidate:hover {
|
|
4874
|
+
color: var(--text);
|
|
4875
|
+
border-color: var(--text-muted);
|
|
4876
|
+
}
|
|
4877
|
+
|
|
4878
|
+
button.jam-candidate[aria-pressed="true"] {
|
|
4879
|
+
color: var(--text);
|
|
4880
|
+
background: var(--surface-2);
|
|
4881
|
+
border-color: var(--text-muted);
|
|
4882
|
+
box-shadow: inset 0 -0.125rem 0 var(--text);
|
|
4883
|
+
}
|
|
4884
|
+
|
|
4885
|
+
button.jam-candidate:focus-visible {
|
|
4886
|
+
outline: 0.125rem solid var(--text);
|
|
4887
|
+
outline-offset: 0.25rem;
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4890
|
+
span.jam-candidate {
|
|
4891
|
+
color: var(--text);
|
|
4892
|
+
}
|
|
4893
|
+
|
|
4894
|
+
/* The declared harmony: a quiet line above the scale block and the label before the scale name.
|
|
4895
|
+
Both are hidden without a declaration. Two plain facts: no accent, no mark of agreement or difference. */
|
|
4896
|
+
#jam-declared {
|
|
4897
|
+
display: flex;
|
|
4898
|
+
flex-direction: row;
|
|
4899
|
+
flex-wrap: wrap;
|
|
4900
|
+
align-items: baseline;
|
|
4901
|
+
column-gap: 0.5rem;
|
|
4902
|
+
margin: 0;
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
#jam-declared[hidden] {
|
|
4906
|
+
display: none;
|
|
4549
4907
|
}
|
|
4550
4908
|
|
|
4551
4909
|
#jam-declared-label,
|
|
@@ -5942,6 +6300,848 @@ button.mx-name:focus-visible {
|
|
|
5942
6300
|
white-space: nowrap;
|
|
5943
6301
|
}
|
|
5944
6302
|
|
|
6303
|
+
/* The Scratchpad view. #scratch-view fills the panel like the Jam view and scrolls on its own. The roll, its ruler and the
|
|
6304
|
+
piano names share one box that scrolls sideways, so their columns stay aligned. The lines are painted with gradients (cycle,
|
|
6305
|
+
beat, grid step and an octave line above each C row), the notes and the cursor are absolutely placed elements that read their
|
|
6306
|
+
place from the custom properties the view sets. Neutral colors only: the accent is for "live now" and red is for the failed-change
|
|
6307
|
+
banner. Sizes in rem, 1px hairlines only, no motion. */
|
|
6308
|
+
#scratch-view {
|
|
6309
|
+
/* Before the view is first sized, and when it has no size to read: the smallest beat and a middle row. The view's sizing sets both
|
|
6310
|
+
again on #scratch-scroll, which the ruler and the roll inherit. */
|
|
6311
|
+
--sp-beat-w: 4rem;
|
|
6312
|
+
--sp-row-h: 0.875rem;
|
|
6313
|
+
/* The rows the roll shows: the view sets the real count on #scratch-roll from the notes, a whole number of octaves (24 to 108). */
|
|
6314
|
+
--sp-rows: 24;
|
|
6315
|
+
--sp-line-cycle: color-mix(in srgb, var(--text-muted) 70%, var(--dominant));
|
|
6316
|
+
--sp-line-beat: color-mix(in srgb, var(--text-muted) 35%, var(--border));
|
|
6317
|
+
--sp-line-step: color-mix(in srgb, var(--border) 60%, transparent);
|
|
6318
|
+
--sp-line-octave: color-mix(in srgb, var(--text-muted) 50%, var(--dominant));
|
|
6319
|
+
--sp-row-natural: color-mix(in srgb, var(--surface-2) 55%, var(--dominant));
|
|
6320
|
+
--sp-row-black: var(--dominant);
|
|
6321
|
+
/* The height of the listening strip: one line, two in layout b. The information line above it is pinned just over it. */
|
|
6322
|
+
--sp-listen-h: 2rem;
|
|
6323
|
+
flex: 1;
|
|
6324
|
+
min-height: 0;
|
|
6325
|
+
overflow: auto;
|
|
6326
|
+
display: flex;
|
|
6327
|
+
flex-direction: column;
|
|
6328
|
+
padding: 0.5rem 1rem;
|
|
6329
|
+
gap: 0.5rem;
|
|
6330
|
+
}
|
|
6331
|
+
|
|
6332
|
+
#scratch-view[hidden] {
|
|
6333
|
+
display: none;
|
|
6334
|
+
}
|
|
6335
|
+
|
|
6336
|
+
#workspace[data-layout="b"] #scratch-view {
|
|
6337
|
+
--sp-listen-h: 4rem;
|
|
6338
|
+
padding: 1rem;
|
|
6339
|
+
}
|
|
6340
|
+
|
|
6341
|
+
#workspace[data-layout="c"] #scratch-view {
|
|
6342
|
+
padding: 1.5rem;
|
|
6343
|
+
}
|
|
6344
|
+
|
|
6345
|
+
#workspace[data-layout="b"] #scratch-scroll,
|
|
6346
|
+
#workspace[data-layout="c"] #scratch-scroll {
|
|
6347
|
+
min-height: 23.5rem;
|
|
6348
|
+
}
|
|
6349
|
+
|
|
6350
|
+
/* The toolbar: the grid, the length of the motif and the pitch window. The buttons are the page's .tl-btn; the grid group takes the
|
|
6351
|
+
segmented look of the Jam instrument switch (a frame, the pressed segment on the raised fill, no weight change). A button that has
|
|
6352
|
+
reached its end keeps focus and shows it with a muted word and a dashed frame. */
|
|
6353
|
+
#scratch-controls {
|
|
6354
|
+
flex: 0 0 auto;
|
|
6355
|
+
display: flex;
|
|
6356
|
+
flex-wrap: wrap;
|
|
6357
|
+
align-items: center;
|
|
6358
|
+
gap: 0.5rem 1rem;
|
|
6359
|
+
}
|
|
6360
|
+
|
|
6361
|
+
#scratch-grids,
|
|
6362
|
+
#scratch-length,
|
|
6363
|
+
#scratch-pitch,
|
|
6364
|
+
#scratch-mode,
|
|
6365
|
+
#scratch-edit,
|
|
6366
|
+
#scratch-history,
|
|
6367
|
+
#scratch-record-group {
|
|
6368
|
+
display: flex;
|
|
6369
|
+
align-items: center;
|
|
6370
|
+
gap: 0.25rem;
|
|
6371
|
+
}
|
|
6372
|
+
|
|
6373
|
+
/* The Early experimental label: the last item of the toolbar, plain text at its right end, no control and no tab stop. */
|
|
6374
|
+
#scratch-early {
|
|
6375
|
+
margin-left: auto;
|
|
6376
|
+
padding: 0.125rem 0.5rem;
|
|
6377
|
+
border: 1px solid var(--border);
|
|
6378
|
+
color: var(--text-muted);
|
|
6379
|
+
font-size: 0.8125rem;
|
|
6380
|
+
line-height: 1.25rem;
|
|
6381
|
+
white-space: nowrap;
|
|
6382
|
+
}
|
|
6383
|
+
|
|
6384
|
+
#scratch-grids {
|
|
6385
|
+
gap: 0;
|
|
6386
|
+
border: 1px solid var(--border);
|
|
6387
|
+
}
|
|
6388
|
+
|
|
6389
|
+
#scratch-grids .tl-btn {
|
|
6390
|
+
background: transparent;
|
|
6391
|
+
border: none;
|
|
6392
|
+
color: var(--text-muted);
|
|
6393
|
+
}
|
|
6394
|
+
|
|
6395
|
+
#scratch-grids .tl-btn:hover:not([aria-disabled="true"]) {
|
|
6396
|
+
color: var(--text);
|
|
6397
|
+
}
|
|
6398
|
+
|
|
6399
|
+
#scratch-grids .tl-btn[aria-pressed="true"] {
|
|
6400
|
+
color: var(--text);
|
|
6401
|
+
background: var(--surface-2);
|
|
6402
|
+
}
|
|
6403
|
+
|
|
6404
|
+
#scratch-grids .tl-btn:focus-visible {
|
|
6405
|
+
outline-offset: -1px;
|
|
6406
|
+
}
|
|
6407
|
+
|
|
6408
|
+
#scratch-controls .tl-btn[aria-disabled="true"] {
|
|
6409
|
+
color: var(--text-muted);
|
|
6410
|
+
}
|
|
6411
|
+
|
|
6412
|
+
/* The Draw and Tap rhythm toggles read pressed by the text color on their frame (the frame of the other buttons is the faint border). */
|
|
6413
|
+
#scratch-draw[aria-pressed="true"],
|
|
6414
|
+
#scratch-tap[aria-pressed="true"] {
|
|
6415
|
+
border-color: var(--text);
|
|
6416
|
+
}
|
|
6417
|
+
|
|
6418
|
+
.sp-readout {
|
|
6419
|
+
box-sizing: border-box;
|
|
6420
|
+
min-width: 4.5rem;
|
|
6421
|
+
padding: 0 0.5rem;
|
|
6422
|
+
color: var(--text);
|
|
6423
|
+
font-family: var(--font-mono);
|
|
6424
|
+
font-size: 0.8125rem;
|
|
6425
|
+
font-weight: 400;
|
|
6426
|
+
line-height: 1.5;
|
|
6427
|
+
text-align: center;
|
|
6428
|
+
white-space: nowrap;
|
|
6429
|
+
}
|
|
6430
|
+
|
|
6431
|
+
/* The scroll box takes the room the view has (its height is never taken from its content, so the rows can follow it) and never less
|
|
6432
|
+
than 22rem. It scrolls both ways: sideways when the beats at their smallest do not fit, and up and down when the rows at their
|
|
6433
|
+
smallest height do not fit (a motif of more than three octaves in a short strip). The ruler is held at the top of the box, the piano
|
|
6434
|
+
names at its left and the velocity lane at its bottom, so only the roll moves. A shorter strip scrolls the view. */
|
|
6435
|
+
#scratch-scroll {
|
|
6436
|
+
flex: 1 1 0%;
|
|
6437
|
+
min-height: 22rem;
|
|
6438
|
+
box-sizing: border-box;
|
|
6439
|
+
border: 1px solid var(--border);
|
|
6440
|
+
background: var(--dominant);
|
|
6441
|
+
/* No padding at the left, the top or the bottom: the piano names, the ruler and the lane stick to those edges of the box, and a padding
|
|
6442
|
+
there would show a sliver of the scrolled roll beside them. The ruler and the lane carry the air themselves. */
|
|
6443
|
+
padding: 0 0.25rem 0 0;
|
|
6444
|
+
overflow: auto;
|
|
6445
|
+
/* The room for a scrollbar is kept whether or not the rows need one, so the beat width worked out from the box does not change when
|
|
6446
|
+
the rows start to scroll. */
|
|
6447
|
+
scrollbar-gutter: stable;
|
|
6448
|
+
/* The ruler and the lane hold a z-index of their own over the roll. Kept inside the box, it cannot rise over the information line and the
|
|
6449
|
+
strip that layout a pins over the bottom of the view (they sit lower), which cut the top of the line's text. */
|
|
6450
|
+
isolation: isolate;
|
|
6451
|
+
}
|
|
6452
|
+
|
|
6453
|
+
/* Held at the top of the box while the roll scrolls up and down; the bar over the names and the grid hides what passes under it. */
|
|
6454
|
+
#scratch-ruler {
|
|
6455
|
+
position: sticky;
|
|
6456
|
+
top: 0;
|
|
6457
|
+
z-index: 3;
|
|
6458
|
+
box-sizing: border-box;
|
|
6459
|
+
display: flex;
|
|
6460
|
+
width: max-content;
|
|
6461
|
+
min-width: 100%;
|
|
6462
|
+
height: 1.5rem;
|
|
6463
|
+
padding-top: 0.25rem;
|
|
6464
|
+
background: var(--dominant);
|
|
6465
|
+
user-select: none;
|
|
6466
|
+
}
|
|
6467
|
+
|
|
6468
|
+
/* The width of the piano names stays at the left while the ruler scrolls, so a label never slides over the names (the column is as wide as
|
|
6469
|
+
the one below, which the fit counts). */
|
|
6470
|
+
#scratch-ruler::before {
|
|
6471
|
+
content: "";
|
|
6472
|
+
position: sticky;
|
|
6473
|
+
left: 0;
|
|
6474
|
+
z-index: 2;
|
|
6475
|
+
flex: none;
|
|
6476
|
+
width: 4.5rem;
|
|
6477
|
+
background: var(--dominant);
|
|
6478
|
+
}
|
|
6479
|
+
|
|
6480
|
+
.sp-beat {
|
|
6481
|
+
flex: none;
|
|
6482
|
+
box-sizing: border-box;
|
|
6483
|
+
width: var(--sp-beat-w);
|
|
6484
|
+
padding-left: 0.25rem;
|
|
6485
|
+
color: var(--text-muted);
|
|
6486
|
+
font-family: var(--font-mono);
|
|
6487
|
+
font-size: 0.8125rem;
|
|
6488
|
+
font-weight: 400;
|
|
6489
|
+
line-height: 1.25rem;
|
|
6490
|
+
white-space: nowrap;
|
|
6491
|
+
}
|
|
6492
|
+
|
|
6493
|
+
.sp-beat-first {
|
|
6494
|
+
color: var(--text);
|
|
6495
|
+
}
|
|
6496
|
+
|
|
6497
|
+
#scratch-roll {
|
|
6498
|
+
display: flex;
|
|
6499
|
+
width: max-content;
|
|
6500
|
+
user-select: none;
|
|
6501
|
+
}
|
|
6502
|
+
|
|
6503
|
+
/* The ring sits just outside the roll: the names and the grid are positioned and would paint over a ring drawn inside it. */
|
|
6504
|
+
#scratch-roll:focus-visible {
|
|
6505
|
+
outline: 1px solid var(--text-muted);
|
|
6506
|
+
outline-offset: 1px;
|
|
6507
|
+
}
|
|
6508
|
+
|
|
6509
|
+
#scratch-gutter {
|
|
6510
|
+
position: sticky;
|
|
6511
|
+
left: 0;
|
|
6512
|
+
z-index: 2;
|
|
6513
|
+
flex: none;
|
|
6514
|
+
width: 4.5rem;
|
|
6515
|
+
background: var(--dominant);
|
|
6516
|
+
}
|
|
6517
|
+
|
|
6518
|
+
.sp-key {
|
|
6519
|
+
box-sizing: border-box;
|
|
6520
|
+
height: var(--sp-row-h);
|
|
6521
|
+
display: flex;
|
|
6522
|
+
align-items: center;
|
|
6523
|
+
justify-content: flex-end;
|
|
6524
|
+
padding-right: 0.25rem;
|
|
6525
|
+
overflow: hidden;
|
|
6526
|
+
background: var(--surface-2);
|
|
6527
|
+
color: var(--text-muted);
|
|
6528
|
+
font-family: var(--font-mono);
|
|
6529
|
+
font-size: 0.8125rem;
|
|
6530
|
+
font-weight: 400;
|
|
6531
|
+
line-height: 1;
|
|
6532
|
+
white-space: nowrap;
|
|
6533
|
+
}
|
|
6534
|
+
|
|
6535
|
+
.sp-key[data-black="true"] {
|
|
6536
|
+
background: var(--dominant);
|
|
6537
|
+
}
|
|
6538
|
+
|
|
6539
|
+
.sp-key[data-cursor="true"] {
|
|
6540
|
+
color: var(--text);
|
|
6541
|
+
}
|
|
6542
|
+
|
|
6543
|
+
/* A key held now: a bar of the accent at the left and its name in the text color, heavier. */
|
|
6544
|
+
.sp-key[data-held="true"] {
|
|
6545
|
+
border-left: 0.25rem solid var(--accent);
|
|
6546
|
+
color: var(--text);
|
|
6547
|
+
font-weight: 600;
|
|
6548
|
+
}
|
|
6549
|
+
|
|
6550
|
+
#scratch-grid {
|
|
6551
|
+
position: relative;
|
|
6552
|
+
flex: none;
|
|
6553
|
+
width: calc(var(--sp-cycles) * 4 * var(--sp-beat-w));
|
|
6554
|
+
height: calc(var(--sp-rows) * var(--sp-row-h));
|
|
6555
|
+
overflow: hidden;
|
|
6556
|
+
user-select: none;
|
|
6557
|
+
touch-action: none;
|
|
6558
|
+
cursor: default;
|
|
6559
|
+
background-color: var(--sp-row-black);
|
|
6560
|
+
background-image:
|
|
6561
|
+
repeating-linear-gradient(to right, var(--sp-line-cycle) 0, var(--sp-line-cycle) 1px, transparent 1px, transparent calc(var(--sp-beat-w) * 4)),
|
|
6562
|
+
repeating-linear-gradient(to right, var(--sp-line-beat) 0, var(--sp-line-beat) 1px, transparent 1px, transparent var(--sp-beat-w)),
|
|
6563
|
+
repeating-linear-gradient(to right, var(--sp-line-step) 0, var(--sp-line-step) 1px, transparent 1px, transparent calc(var(--sp-beat-w) * 4 / var(--sp-steps))),
|
|
6564
|
+
linear-gradient(to bottom, var(--sp-line-octave) 0, var(--sp-line-octave) 1px, transparent 1px),
|
|
6565
|
+
linear-gradient(
|
|
6566
|
+
to bottom,
|
|
6567
|
+
var(--sp-row-natural) 0,
|
|
6568
|
+
var(--sp-row-natural) var(--sp-row-h),
|
|
6569
|
+
var(--sp-row-black) var(--sp-row-h),
|
|
6570
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 2),
|
|
6571
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 2),
|
|
6572
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 3),
|
|
6573
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 3),
|
|
6574
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 4),
|
|
6575
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 4),
|
|
6576
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 5),
|
|
6577
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 5),
|
|
6578
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 6),
|
|
6579
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 6),
|
|
6580
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 8),
|
|
6581
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 8),
|
|
6582
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 9),
|
|
6583
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 9),
|
|
6584
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 10),
|
|
6585
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 10),
|
|
6586
|
+
var(--sp-row-black) calc(var(--sp-row-h) * 11),
|
|
6587
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 11),
|
|
6588
|
+
var(--sp-row-natural) calc(var(--sp-row-h) * 12)
|
|
6589
|
+
);
|
|
6590
|
+
background-size: auto, auto, auto, 100% calc(var(--sp-row-h) * 12), 100% calc(var(--sp-row-h) * 12);
|
|
6591
|
+
background-position: 0 0, 0 0, 0 0, 0 calc(var(--sp-row-h) * -1), 0 0;
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
/* The pointer is a crosshair on empty cells only while Draw is on: a drag then writes a note, else it draws a selection box. */
|
|
6595
|
+
#scratch-view[data-draw="on"] #scratch-grid {
|
|
6596
|
+
cursor: crosshair;
|
|
6597
|
+
}
|
|
6598
|
+
|
|
6599
|
+
#scratch-notes {
|
|
6600
|
+
position: absolute;
|
|
6601
|
+
inset: 0;
|
|
6602
|
+
}
|
|
6603
|
+
|
|
6604
|
+
.sp-note,
|
|
6605
|
+
.sp-live,
|
|
6606
|
+
#scratch-cursor,
|
|
6607
|
+
#scratch-preview,
|
|
6608
|
+
#scratch-hover-cell {
|
|
6609
|
+
position: absolute;
|
|
6610
|
+
box-sizing: border-box;
|
|
6611
|
+
left: calc(var(--sp-start) / var(--sp-units) * 100% + 0.125rem);
|
|
6612
|
+
width: calc(var(--sp-len) / var(--sp-units) * 100% - 0.25rem);
|
|
6613
|
+
top: calc(var(--sp-row) * var(--sp-row-h) + 0.125rem);
|
|
6614
|
+
height: calc(var(--sp-row-h) - 0.25rem);
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6617
|
+
.sp-note {
|
|
6618
|
+
background: color-mix(in srgb, var(--text) calc(30% + 60% * var(--v)), var(--dominant));
|
|
6619
|
+
border: 1px solid var(--text-muted);
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6622
|
+
.sp-note {
|
|
6623
|
+
cursor: grab;
|
|
6624
|
+
}
|
|
6625
|
+
|
|
6626
|
+
/* The live layer: the keyboard buffer's notes, drawn over the motif in the accent ("live now", as the Timeline playhead is), translucent so a
|
|
6627
|
+
motif note under them stays visible. A note still held has no right border: its right edge is open and follows the playhead. The layer
|
|
6628
|
+
and its notes take no pointer event, so the motif underneath stays editable. */
|
|
6629
|
+
#scratch-live {
|
|
6630
|
+
position: absolute;
|
|
6631
|
+
inset: 0;
|
|
6632
|
+
pointer-events: none;
|
|
6633
|
+
}
|
|
6634
|
+
|
|
6635
|
+
.sp-live {
|
|
6636
|
+
width: max(0.25rem, calc(var(--sp-len) / var(--sp-units) * 100% - 0.25rem));
|
|
6637
|
+
background: color-mix(in srgb, var(--accent) calc(15% + 25% * var(--v)), transparent);
|
|
6638
|
+
border: 1px solid var(--accent);
|
|
6639
|
+
pointer-events: none;
|
|
6640
|
+
}
|
|
6641
|
+
|
|
6642
|
+
.sp-live[data-held="true"] {
|
|
6643
|
+
border-right-width: 0;
|
|
6644
|
+
}
|
|
6645
|
+
|
|
6646
|
+
/* The handle at a note's right side: half a rem wide, at most 40 percent of a short note. It only changes the cursor; the press is
|
|
6647
|
+
told apart from the numbers of the pointer in page/scratch-gesture.js. */
|
|
6648
|
+
.sp-edge {
|
|
6649
|
+
position: absolute;
|
|
6650
|
+
top: 0;
|
|
6651
|
+
right: 0;
|
|
6652
|
+
bottom: 0;
|
|
6653
|
+
width: min(0.5rem, 40%);
|
|
6654
|
+
cursor: ew-resize;
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6657
|
+
.sp-hover {
|
|
6658
|
+
outline: 1px solid var(--text);
|
|
6659
|
+
}
|
|
6660
|
+
|
|
6661
|
+
.sp-selected {
|
|
6662
|
+
outline: 0.125rem solid var(--text);
|
|
6663
|
+
}
|
|
6664
|
+
|
|
6665
|
+
/* A drag of several selected notes draws each of them where the drop would put it, in the dashed ring of a preview. */
|
|
6666
|
+
.sp-draft {
|
|
6667
|
+
outline-style: dashed;
|
|
6668
|
+
}
|
|
6669
|
+
|
|
6670
|
+
/* A draw, move or resize in progress: the dashed ring of the cursor, over the notes. The hovered empty cell is a thin outline, so the
|
|
6671
|
+
two are never confused. Neither takes a pointer event: the grid does. */
|
|
6672
|
+
#scratch-preview {
|
|
6673
|
+
border: 0.125rem dashed var(--text);
|
|
6674
|
+
pointer-events: none;
|
|
6675
|
+
}
|
|
6676
|
+
|
|
6677
|
+
#scratch-preview[hidden] {
|
|
6678
|
+
display: none;
|
|
6679
|
+
}
|
|
6680
|
+
|
|
6681
|
+
#scratch-hover-cell {
|
|
6682
|
+
outline: 1px solid var(--text-muted);
|
|
6683
|
+
pointer-events: none;
|
|
6684
|
+
}
|
|
6685
|
+
|
|
6686
|
+
#scratch-hover-cell[hidden] {
|
|
6687
|
+
display: none;
|
|
6688
|
+
}
|
|
6689
|
+
|
|
6690
|
+
/* The selection box of a drag in Select mode: a thin dashed outline and a faint fill, placed by the fractions of units and rows the
|
|
6691
|
+
drag spans. It takes no pointer event: the grid does. */
|
|
6692
|
+
#scratch-select-rect {
|
|
6693
|
+
position: absolute;
|
|
6694
|
+
box-sizing: border-box;
|
|
6695
|
+
left: calc(var(--sp-start) / var(--sp-units) * 100%);
|
|
6696
|
+
width: calc(var(--sp-len) / var(--sp-units) * 100%);
|
|
6697
|
+
top: calc(var(--sp-row) * var(--sp-row-h));
|
|
6698
|
+
height: calc(var(--sp-rows-span) * var(--sp-row-h));
|
|
6699
|
+
border: 1px dashed var(--text);
|
|
6700
|
+
background: color-mix(in srgb, var(--text) 8%, transparent);
|
|
6701
|
+
pointer-events: none;
|
|
6702
|
+
}
|
|
6703
|
+
|
|
6704
|
+
#scratch-select-rect[hidden] {
|
|
6705
|
+
display: none;
|
|
6706
|
+
}
|
|
6707
|
+
|
|
6708
|
+
#scratch-cursor {
|
|
6709
|
+
border: 0.125rem dashed var(--text);
|
|
6710
|
+
pointer-events: none;
|
|
6711
|
+
}
|
|
6712
|
+
|
|
6713
|
+
#scratch-cursor[hidden] {
|
|
6714
|
+
display: none;
|
|
6715
|
+
}
|
|
6716
|
+
|
|
6717
|
+
/* Tap's now-marker: a 1px line of the text color where the music is in the motif. It moves because the music moves, so it moves in
|
|
6718
|
+
both motion modes and has no transition. It takes no pointer event. */
|
|
6719
|
+
#scratch-now {
|
|
6720
|
+
position: absolute;
|
|
6721
|
+
top: 0;
|
|
6722
|
+
bottom: 0;
|
|
6723
|
+
left: calc(var(--sp-now) / var(--sp-units) * 100%);
|
|
6724
|
+
width: 1px;
|
|
6725
|
+
background: var(--text);
|
|
6726
|
+
pointer-events: none;
|
|
6727
|
+
}
|
|
6728
|
+
|
|
6729
|
+
#scratch-now[hidden] {
|
|
6730
|
+
display: none;
|
|
6731
|
+
}
|
|
6732
|
+
|
|
6733
|
+
/* The live playhead: where the cycle axis is on the ring of 8 cycles. Like the marker above it is a position readout, so it has no
|
|
6734
|
+
transition and moves in both motion modes. */
|
|
6735
|
+
#scratch-playhead {
|
|
6736
|
+
position: absolute;
|
|
6737
|
+
top: 0;
|
|
6738
|
+
bottom: 0;
|
|
6739
|
+
left: calc(var(--sp-playhead) / var(--sp-units) * 100%);
|
|
6740
|
+
width: 0.125rem;
|
|
6741
|
+
background: var(--accent);
|
|
6742
|
+
pointer-events: none;
|
|
6743
|
+
}
|
|
6744
|
+
|
|
6745
|
+
#scratch-playhead[hidden] {
|
|
6746
|
+
display: none;
|
|
6747
|
+
}
|
|
6748
|
+
|
|
6749
|
+
/* The velocity lane: one bar per note under its start column, sharing the roll's columns inside the same scroll box, so a sideways
|
|
6750
|
+
scroll moves both. Notes that start together sit side by side in their column, each bar at least a quarter rem wide. The bars take
|
|
6751
|
+
no pointer event: the lane's bars area hears the press and finds the column from the pointer's place, so the whole column answers.
|
|
6752
|
+
The lane is held at the bottom of the box while the roll scrolls up and down, with a fill so nothing shows through. */
|
|
6753
|
+
#scratch-lane {
|
|
6754
|
+
position: sticky;
|
|
6755
|
+
bottom: 0;
|
|
6756
|
+
z-index: 3;
|
|
6757
|
+
display: flex;
|
|
6758
|
+
width: max-content;
|
|
6759
|
+
height: 3.5rem;
|
|
6760
|
+
padding-bottom: 0.25rem;
|
|
6761
|
+
background: var(--dominant);
|
|
6762
|
+
user-select: none;
|
|
6763
|
+
}
|
|
6764
|
+
|
|
6765
|
+
#workspace[data-layout="b"] #scratch-lane,
|
|
6766
|
+
#workspace[data-layout="c"] #scratch-lane {
|
|
6767
|
+
height: 5rem;
|
|
6768
|
+
}
|
|
6769
|
+
|
|
6770
|
+
#scratch-lane:focus-visible {
|
|
6771
|
+
outline: 1px solid var(--text-muted);
|
|
6772
|
+
outline-offset: 1px;
|
|
6773
|
+
}
|
|
6774
|
+
|
|
6775
|
+
#scratch-lane-label {
|
|
6776
|
+
position: sticky;
|
|
6777
|
+
left: 0;
|
|
6778
|
+
z-index: 2;
|
|
6779
|
+
flex: none;
|
|
6780
|
+
box-sizing: border-box;
|
|
6781
|
+
width: 4.5rem;
|
|
6782
|
+
padding-right: 0.25rem;
|
|
6783
|
+
background: var(--dominant);
|
|
6784
|
+
color: var(--text-muted);
|
|
6785
|
+
font-family: var(--font-mono);
|
|
6786
|
+
font-size: 0.8125rem;
|
|
6787
|
+
font-weight: 400;
|
|
6788
|
+
line-height: 1.5;
|
|
6789
|
+
text-align: right;
|
|
6790
|
+
}
|
|
6791
|
+
|
|
6792
|
+
#scratch-bars {
|
|
6793
|
+
position: relative;
|
|
6794
|
+
flex: none;
|
|
6795
|
+
box-sizing: border-box;
|
|
6796
|
+
width: calc(var(--sp-cycles) * 4 * var(--sp-beat-w));
|
|
6797
|
+
height: 100%;
|
|
6798
|
+
border-top: 1px solid var(--border);
|
|
6799
|
+
touch-action: none;
|
|
6800
|
+
cursor: ns-resize;
|
|
6801
|
+
background-image:
|
|
6802
|
+
repeating-linear-gradient(to right, var(--sp-line-cycle) 0, var(--sp-line-cycle) 1px, transparent 1px, transparent calc(var(--sp-beat-w) * 4)),
|
|
6803
|
+
repeating-linear-gradient(to right, var(--sp-line-beat) 0, var(--sp-line-beat) 1px, transparent 1px, transparent var(--sp-beat-w));
|
|
6804
|
+
}
|
|
6805
|
+
|
|
6806
|
+
.sp-bar {
|
|
6807
|
+
--sp-slot-w: max(0.375rem, calc(var(--sp-step) / var(--sp-slots) / var(--sp-units) * 100%));
|
|
6808
|
+
position: absolute;
|
|
6809
|
+
bottom: 0;
|
|
6810
|
+
box-sizing: border-box;
|
|
6811
|
+
left: calc(var(--sp-start) / var(--sp-units) * 100% + var(--sp-slot) * var(--sp-slot-w) + 0.0625rem);
|
|
6812
|
+
width: max(0.25rem, calc(var(--sp-slot-w) - 0.125rem));
|
|
6813
|
+
height: calc(var(--v) * 100%);
|
|
6814
|
+
min-height: 1px;
|
|
6815
|
+
background: var(--text-muted);
|
|
6816
|
+
pointer-events: none;
|
|
6817
|
+
}
|
|
6818
|
+
|
|
6819
|
+
.sp-bar.sp-selected {
|
|
6820
|
+
outline: none;
|
|
6821
|
+
background: var(--text);
|
|
6822
|
+
}
|
|
6823
|
+
|
|
6824
|
+
#scratch-note {
|
|
6825
|
+
position: relative;
|
|
6826
|
+
flex: 0 0 auto;
|
|
6827
|
+
box-sizing: border-box;
|
|
6828
|
+
display: flex;
|
|
6829
|
+
flex-wrap: wrap;
|
|
6830
|
+
align-items: center;
|
|
6831
|
+
gap: 0.25rem 1rem;
|
|
6832
|
+
min-height: 2rem;
|
|
6833
|
+
font-size: 0.8125rem;
|
|
6834
|
+
font-weight: 400;
|
|
6835
|
+
line-height: 1.5;
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
#workspace[data-layout="b"] #scratch-note {
|
|
6839
|
+
min-height: 4rem;
|
|
6840
|
+
}
|
|
6841
|
+
|
|
6842
|
+
/* In layout a the panel is a short band under the editor: the roll keeps its minimum height and the view scrolls. The information line and the
|
|
6843
|
+
listening strip stay at the bottom edge of the view, in sight, because the strip is the one place that says why nothing is heard. */
|
|
6844
|
+
#workspace[data-layout="a"] #scratch-note,
|
|
6845
|
+
#workspace[data-layout="a"] #scratch-listen {
|
|
6846
|
+
position: sticky;
|
|
6847
|
+
z-index: 1;
|
|
6848
|
+
background: var(--secondary);
|
|
6849
|
+
}
|
|
6850
|
+
|
|
6851
|
+
#workspace[data-layout="a"] #scratch-note {
|
|
6852
|
+
bottom: calc(var(--sp-listen-h) + 0.5rem);
|
|
6853
|
+
}
|
|
6854
|
+
|
|
6855
|
+
/* Scrolling a control into view with "nearest" (a focused or revealed lane, a key) must stop above the two pinned lines, not under them: the
|
|
6856
|
+
padding is the listening strip, the information line at its smallest height and the gaps between them. */
|
|
6857
|
+
#workspace[data-layout="a"] #scratch-view {
|
|
6858
|
+
scroll-padding-bottom: calc(var(--sp-listen-h) + 3rem);
|
|
6859
|
+
}
|
|
6860
|
+
|
|
6861
|
+
#workspace[data-layout="a"] #scratch-listen {
|
|
6862
|
+
bottom: 0;
|
|
6863
|
+
}
|
|
6864
|
+
|
|
6865
|
+
#scratch-note-line {
|
|
6866
|
+
flex: 1 1 auto;
|
|
6867
|
+
min-width: 0;
|
|
6868
|
+
margin: 0;
|
|
6869
|
+
color: var(--text-muted);
|
|
6870
|
+
overflow-wrap: anywhere;
|
|
6871
|
+
}
|
|
6872
|
+
|
|
6873
|
+
#scratch-note-name {
|
|
6874
|
+
font-weight: 600;
|
|
6875
|
+
color: var(--text);
|
|
6876
|
+
}
|
|
6877
|
+
|
|
6878
|
+
#scratch-note-rest {
|
|
6879
|
+
font-weight: 400;
|
|
6880
|
+
color: var(--text-muted);
|
|
6881
|
+
font-variant-numeric: tabular-nums;
|
|
6882
|
+
}
|
|
6883
|
+
|
|
6884
|
+
#scratch-note-line[data-kind="notice"] #scratch-note-rest {
|
|
6885
|
+
color: var(--text);
|
|
6886
|
+
}
|
|
6887
|
+
|
|
6888
|
+
/* The line of a hovered note or of a draw in progress: it takes the place of the shown line and is never read aloud (the words for a
|
|
6889
|
+
screen reader stay in #scratch-note-spoken, which a hover does not touch). */
|
|
6890
|
+
#scratch-note[data-hover="true"] #scratch-note-shown {
|
|
6891
|
+
visibility: hidden;
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6894
|
+
#scratch-note-hover {
|
|
6895
|
+
position: absolute;
|
|
6896
|
+
left: 0;
|
|
6897
|
+
top: 0;
|
|
6898
|
+
bottom: 0;
|
|
6899
|
+
display: flex;
|
|
6900
|
+
align-items: center;
|
|
6901
|
+
margin: 0;
|
|
6902
|
+
max-width: 100%;
|
|
6903
|
+
color: var(--text-muted);
|
|
6904
|
+
overflow-wrap: anywhere;
|
|
6905
|
+
font-variant-numeric: tabular-nums;
|
|
6906
|
+
}
|
|
6907
|
+
|
|
6908
|
+
#scratch-note-hover[hidden] {
|
|
6909
|
+
display: none;
|
|
6910
|
+
}
|
|
6911
|
+
|
|
6912
|
+
.sp-hover-name {
|
|
6913
|
+
font-weight: 600;
|
|
6914
|
+
color: var(--text);
|
|
6915
|
+
}
|
|
6916
|
+
|
|
6917
|
+
/* Visually hidden, kept in the accessibility tree: the live region reads this full-words form of the line. */
|
|
6918
|
+
#scratch-note-spoken {
|
|
6919
|
+
position: absolute;
|
|
6920
|
+
width: 1px;
|
|
6921
|
+
height: 1px;
|
|
6922
|
+
overflow: hidden;
|
|
6923
|
+
clip-path: inset(50%);
|
|
6924
|
+
white-space: nowrap;
|
|
6925
|
+
}
|
|
6926
|
+
|
|
6927
|
+
#scratch-hint {
|
|
6928
|
+
flex: 0 1 auto;
|
|
6929
|
+
margin: 0;
|
|
6930
|
+
color: var(--text-muted);
|
|
6931
|
+
}
|
|
6932
|
+
|
|
6933
|
+
/* The listening strip under the information line: whether a MIDI keyboard is listened to, and the last note it played. It keeps a fixed
|
|
6934
|
+
height like the information line (one line, two in layout b); a longer sentence ends in an ellipsis and carries its whole text in its
|
|
6935
|
+
title. Only the state sentence is a live region, so a jam never floods a screen reader note by note. */
|
|
6936
|
+
#scratch-listen {
|
|
6937
|
+
flex: 0 0 auto;
|
|
6938
|
+
box-sizing: border-box;
|
|
6939
|
+
display: flex;
|
|
6940
|
+
align-items: center;
|
|
6941
|
+
gap: 0.25rem 1rem;
|
|
6942
|
+
height: var(--sp-listen-h);
|
|
6943
|
+
overflow: hidden;
|
|
6944
|
+
font-size: 0.8125rem;
|
|
6945
|
+
font-weight: 400;
|
|
6946
|
+
line-height: 1.5;
|
|
6947
|
+
}
|
|
6948
|
+
|
|
6949
|
+
#scratch-listen-state,
|
|
6950
|
+
#scratch-heard {
|
|
6951
|
+
margin: 0;
|
|
6952
|
+
min-width: 0;
|
|
6953
|
+
overflow: hidden;
|
|
6954
|
+
text-overflow: ellipsis;
|
|
6955
|
+
white-space: nowrap;
|
|
6956
|
+
}
|
|
6957
|
+
|
|
6958
|
+
#workspace[data-layout="b"] #scratch-listen-state,
|
|
6959
|
+
#workspace[data-layout="b"] #scratch-heard {
|
|
6960
|
+
display: -webkit-box;
|
|
6961
|
+
-webkit-box-orient: vertical;
|
|
6962
|
+
-webkit-line-clamp: 2;
|
|
6963
|
+
white-space: normal;
|
|
6964
|
+
overflow-wrap: anywhere;
|
|
6965
|
+
}
|
|
6966
|
+
|
|
6967
|
+
#scratch-listen-state {
|
|
6968
|
+
flex: 1 1 auto;
|
|
6969
|
+
color: var(--text-muted);
|
|
6970
|
+
}
|
|
6971
|
+
|
|
6972
|
+
/* The Allow MIDI to listen button sits after the state sentence; it shows only while asking can help, and its own rule keeps it hidden. */
|
|
6973
|
+
#scratch-allow {
|
|
6974
|
+
flex: 0 0 auto;
|
|
6975
|
+
}
|
|
6976
|
+
|
|
6977
|
+
#scratch-allow[hidden] {
|
|
6978
|
+
display: none;
|
|
6979
|
+
}
|
|
6980
|
+
|
|
6981
|
+
#scratch-heard {
|
|
6982
|
+
flex: 0 1 auto;
|
|
6983
|
+
max-width: 50%;
|
|
6984
|
+
color: var(--text);
|
|
6985
|
+
font-variant-numeric: tabular-nums;
|
|
6986
|
+
}
|
|
6987
|
+
|
|
6988
|
+
/* The hear strip under the keyboard strip: the menu of instruments and channels, the Start choice, Loop, Listen and Stop on one row that wraps, and
|
|
6989
|
+
under them the strip's one live region, the sentence that says what became of a play. Like the send strip it scrolls with the view. The menu has
|
|
6990
|
+
the look of the words box of the send strip, the Start choice the segmented look of the grid group, and a long name is cut inside the menu. */
|
|
6991
|
+
#scratch-play {
|
|
6992
|
+
flex: 0 0 auto;
|
|
6993
|
+
box-sizing: border-box;
|
|
6994
|
+
display: flex;
|
|
6995
|
+
flex-wrap: wrap;
|
|
6996
|
+
align-items: center;
|
|
6997
|
+
gap: 0.25rem 0.5rem;
|
|
6998
|
+
font-size: 0.8125rem;
|
|
6999
|
+
font-weight: 400;
|
|
7000
|
+
line-height: 1.5;
|
|
7001
|
+
}
|
|
7002
|
+
|
|
7003
|
+
#scratch-play-output {
|
|
7004
|
+
flex: 0 1 16rem;
|
|
7005
|
+
min-width: 0;
|
|
7006
|
+
max-width: 100%;
|
|
7007
|
+
box-sizing: border-box;
|
|
7008
|
+
height: 2rem;
|
|
7009
|
+
padding: 0 0.5rem;
|
|
7010
|
+
background: var(--dominant);
|
|
7011
|
+
color: var(--text);
|
|
7012
|
+
border: 1px solid var(--border);
|
|
7013
|
+
font-family: inherit;
|
|
7014
|
+
font-size: 0.8125rem;
|
|
7015
|
+
line-height: 1.5;
|
|
7016
|
+
overflow: hidden;
|
|
7017
|
+
text-overflow: ellipsis;
|
|
7018
|
+
white-space: nowrap;
|
|
7019
|
+
}
|
|
7020
|
+
|
|
7021
|
+
#scratch-play-output:focus-visible {
|
|
7022
|
+
outline: 1px solid var(--text-muted);
|
|
7023
|
+
outline-offset: 0;
|
|
7024
|
+
}
|
|
7025
|
+
|
|
7026
|
+
#scratch-play-start {
|
|
7027
|
+
display: flex;
|
|
7028
|
+
align-items: center;
|
|
7029
|
+
gap: 0;
|
|
7030
|
+
border: 1px solid var(--border);
|
|
7031
|
+
}
|
|
7032
|
+
|
|
7033
|
+
#scratch-play-start .tl-btn {
|
|
7034
|
+
background: transparent;
|
|
7035
|
+
border: none;
|
|
7036
|
+
color: var(--text-muted);
|
|
7037
|
+
}
|
|
7038
|
+
|
|
7039
|
+
#scratch-play-start .tl-btn:hover:not([aria-disabled="true"]) {
|
|
7040
|
+
color: var(--text);
|
|
7041
|
+
}
|
|
7042
|
+
|
|
7043
|
+
#scratch-play-start .tl-btn[aria-pressed="true"] {
|
|
7044
|
+
color: var(--text);
|
|
7045
|
+
background: var(--surface-2);
|
|
7046
|
+
}
|
|
7047
|
+
|
|
7048
|
+
#scratch-play-start .tl-btn:focus-visible {
|
|
7049
|
+
outline-offset: -1px;
|
|
7050
|
+
}
|
|
7051
|
+
|
|
7052
|
+
#scratch-play .tl-btn[aria-disabled="true"] {
|
|
7053
|
+
color: var(--text-muted);
|
|
7054
|
+
}
|
|
7055
|
+
|
|
7056
|
+
#scratch-play-loop[aria-pressed="true"] {
|
|
7057
|
+
border-color: var(--text);
|
|
7058
|
+
}
|
|
7059
|
+
|
|
7060
|
+
#scratch-play-note {
|
|
7061
|
+
flex: 1 0 100%;
|
|
7062
|
+
margin: 0;
|
|
7063
|
+
min-height: 1.25rem;
|
|
7064
|
+
color: var(--text);
|
|
7065
|
+
overflow-wrap: anywhere;
|
|
7066
|
+
font-variant-numeric: tabular-nums;
|
|
7067
|
+
}
|
|
7068
|
+
|
|
7069
|
+
.sp-hear-id {
|
|
7070
|
+
font-weight: 600;
|
|
7071
|
+
}
|
|
7072
|
+
|
|
7073
|
+
/* The send strip, the last block of the Scratchpad: the phrase of the motif (the very text a send carries, in a box of its own height with its own
|
|
7074
|
+
scroll, wrapping at any character), the words for Claude with the Send to Claude button beside them, and the status line, the strip's one live
|
|
7075
|
+
region. It scrolls with the view; the phrase can be thousands of characters and is never a live region. */
|
|
7076
|
+
#scratch-send {
|
|
7077
|
+
flex: 0 0 auto;
|
|
7078
|
+
box-sizing: border-box;
|
|
7079
|
+
display: flex;
|
|
7080
|
+
flex-direction: column;
|
|
7081
|
+
gap: 0.25rem;
|
|
7082
|
+
font-size: 0.8125rem;
|
|
7083
|
+
font-weight: 400;
|
|
7084
|
+
line-height: 1.5;
|
|
7085
|
+
}
|
|
7086
|
+
|
|
7087
|
+
#scratch-phrase {
|
|
7088
|
+
display: block;
|
|
7089
|
+
box-sizing: border-box;
|
|
7090
|
+
max-height: 4.5rem;
|
|
7091
|
+
overflow: auto;
|
|
7092
|
+
padding: 0.25rem 0.5rem;
|
|
7093
|
+
background: var(--dominant);
|
|
7094
|
+
color: var(--text);
|
|
7095
|
+
border: 1px solid var(--border);
|
|
7096
|
+
font-family: var(--font-mono);
|
|
7097
|
+
font-size: 0.75rem;
|
|
7098
|
+
line-height: 1.5;
|
|
7099
|
+
overflow-wrap: anywhere;
|
|
7100
|
+
}
|
|
7101
|
+
|
|
7102
|
+
#scratch-phrase[data-empty="true"] {
|
|
7103
|
+
color: var(--text-muted);
|
|
7104
|
+
font-family: inherit;
|
|
7105
|
+
font-size: 0.8125rem;
|
|
7106
|
+
}
|
|
7107
|
+
|
|
7108
|
+
#scratch-phrase:focus-visible,
|
|
7109
|
+
#scratch-words:focus-visible {
|
|
7110
|
+
outline: 1px solid var(--text-muted);
|
|
7111
|
+
outline-offset: 0;
|
|
7112
|
+
}
|
|
7113
|
+
|
|
7114
|
+
#scratch-send-row {
|
|
7115
|
+
display: flex;
|
|
7116
|
+
align-items: center;
|
|
7117
|
+
gap: 0.5rem;
|
|
7118
|
+
}
|
|
7119
|
+
|
|
7120
|
+
#scratch-words {
|
|
7121
|
+
flex: 1 1 auto;
|
|
7122
|
+
min-width: 0;
|
|
7123
|
+
box-sizing: border-box;
|
|
7124
|
+
height: 2rem;
|
|
7125
|
+
padding: 0 0.5rem;
|
|
7126
|
+
background: var(--dominant);
|
|
7127
|
+
color: var(--text);
|
|
7128
|
+
border: 1px solid var(--border);
|
|
7129
|
+
font-family: inherit;
|
|
7130
|
+
font-size: 0.8125rem;
|
|
7131
|
+
line-height: 1.5;
|
|
7132
|
+
}
|
|
7133
|
+
|
|
7134
|
+
#scratch-send-button {
|
|
7135
|
+
flex: 0 0 auto;
|
|
7136
|
+
}
|
|
7137
|
+
|
|
7138
|
+
#scratch-send-status {
|
|
7139
|
+
margin: 0;
|
|
7140
|
+
min-height: 1.25rem;
|
|
7141
|
+
color: var(--text);
|
|
7142
|
+
overflow-wrap: anywhere;
|
|
7143
|
+
}
|
|
7144
|
+
|
|
5945
7145
|
/* The load failure notice, for a page whose Strudel or acorn script did not load. It stands alone: it needs nothing
|
|
5946
7146
|
from the rest of the page, which may never start. The base rule is the strip of the partial case (acorn missing,
|
|
5947
7147
|
everything else works): in flow above the bar, the look of a kind-notice failed-change banner, one level above the
|