Spaces:
Running
UI redesign Phase 0-6 + functional fixes
Browse files- tokens.css v3: chrome-only design tokens (slate + amber)
- corrector.css: full retoken, no pink in chrome, TL note 56px
- corrector.html: mode-panel, multi-edit panel, proxy scripts
- Phase 3: status bar restyle, shortcut-bar hidden, feedback collapsed
- Phase 4: left mode-panel with proxied mode/display/offset controls
- Phase 5: barline toolbar coral context-bar + marker dimming
- Phase 6: multi-edit panel for batch operations on multi-select
- Fix: staff drag now updates omrY so markers don't snap back
- Fix: free glyph click navigates timeline to that measure
- Fix: auto-fit zoom on image load (width-fit)
- Fix: acc-label size 46px, closer to note (+8,+5)
- Add: pitch preview sound on raise/lower/accidental/drag
- Update: glyph shapes with correct unicode symbols
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- static/corrector.css +202 -112
- static/corrector.html +120 -13
- static/corrector.js +92 -12
- static/tokens.css +79 -132
|
@@ -1,92 +1,144 @@
|
|
| 1 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 2 |
-
body { font-family:
|
| 3 |
|
| 4 |
/* Feedback Banner */
|
| 5 |
-
#feedback-bar {
|
| 6 |
-
display: flex; justify-content: center; padding: 6px 12px;
|
| 7 |
-
background: #1a0a2e; border-bottom: 1px solid #444;
|
| 8 |
-
}
|
| 9 |
#feedback-btn {
|
| 10 |
display: inline-block; padding: 8px 40px;
|
| 11 |
-
background:
|
| 12 |
text-decoration: none; border-radius: 6px; letter-spacing: 1px;
|
| 13 |
}
|
| 14 |
-
#feedback-btn:hover { background: #
|
| 15 |
|
| 16 |
/* Upload Bar */
|
| 17 |
#upload-bar {
|
| 18 |
display: flex; align-items: center; gap: 15px; padding: 8px 12px;
|
| 19 |
-
background:
|
| 20 |
-
font-size:
|
| 21 |
-
}
|
| 22 |
-
#upload-bar label { display: flex; align-items: center; gap: 6px; font-size:
|
| 23 |
-
#upload-bar input[type="file"] { width: 340px; font-size:
|
| 24 |
-
#upload-bar input[type="number"] { width: 75px; background:
|
| 25 |
-
#load-btn { padding: 8px 22px; background: #
|
| 26 |
-
#load-btn:hover {
|
| 27 |
-
#load-status { color:
|
| 28 |
#page-nav { display: inline-flex; align-items: center; gap: 6px; }
|
| 29 |
-
#page-nav button { padding: 4px 12px; background:
|
| 30 |
-
#page-nav button:hover { background:
|
| 31 |
-
#page-indicator { font-size:
|
| 32 |
|
| 33 |
/* Toolbar */
|
| 34 |
#toolbar {
|
| 35 |
display: flex; align-items: center; gap: 8px; padding: 6px 12px;
|
| 36 |
-
background:
|
| 37 |
-
font-size:
|
| 38 |
}
|
| 39 |
-
.tool-group { display: flex; align-items: center; gap: 4px; padding: 0 6px; border-right: 1px solid
|
| 40 |
.tool-group:last-child { border-right: none; }
|
| 41 |
.tool-group.right { margin-left: auto; }
|
| 42 |
#toolbar button {
|
| 43 |
-
padding: 6px 11px; background:
|
| 44 |
-
border-radius: 3px; cursor: pointer; font-size:
|
| 45 |
-
}
|
| 46 |
-
#toolbar button:hover { background:
|
| 47 |
-
#toolbar button:active { background:
|
| 48 |
-
#btn-show-free-glyphs { padding: 10px 18px; font-size:
|
| 49 |
-
#btn-show-free-glyphs:hover { background:
|
| 50 |
-
#btn-show-free-glyphs.active { background:
|
| 51 |
-
#btn-download { background:
|
| 52 |
-
#btn-download:hover { background:
|
| 53 |
-
#
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
#toolbar input[type="range"] { width: 112px; height: 12px; }
|
| 56 |
|
| 57 |
#zoom-slider { width: 112px; vertical-align: middle; }
|
| 58 |
-
#zoom-label { font-size:
|
| 59 |
|
| 60 |
/* Shortcut Reference Bar */
|
| 61 |
-
#shortcut-bar {
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
}
|
| 66 |
-
#shortcut-bar b { color: #bbb; }
|
| 67 |
|
| 68 |
/* Progress Bar */
|
| 69 |
#progress-bar-container {
|
| 70 |
-
position: relative; height: 38px; background:
|
| 71 |
cursor: pointer; user-select: none;
|
| 72 |
}
|
| 73 |
#progress-bar-fill {
|
| 74 |
-
height: 100%; width: 0%; background: linear-gradient(90deg,
|
| 75 |
transition: none; pointer-events: none;
|
| 76 |
}
|
| 77 |
#progress-time {
|
| 78 |
position: absolute; top: 0; right: 8px; line-height: 38px;
|
| 79 |
-
font-size:
|
| 80 |
}
|
| 81 |
|
| 82 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
#main-area {
|
| 84 |
flex: 1; display: flex; overflow: hidden; min-height: 0;
|
| 85 |
}
|
| 86 |
|
| 87 |
/* Main Canvas */
|
| 88 |
#canvas-wrapper {
|
| 89 |
-
flex: 1; overflow: auto; background:
|
| 90 |
}
|
| 91 |
#canvas-container {
|
| 92 |
position: relative; display: inline-block;
|
|
@@ -99,6 +151,10 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 99 |
position: absolute; top: 0; left: 0; pointer-events: none;
|
| 100 |
}
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
/* Markers */
|
| 103 |
.marker {
|
| 104 |
pointer-events: all; cursor: pointer;
|
|
@@ -123,12 +179,11 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 123 |
.marker.grade-mid { fill: rgba(255, 165, 0, 0.8); stroke: #ffa500; stroke-width: 1.5; }
|
| 124 |
.marker.grade-ok { fill: rgba(255, 220, 50, 0.7); stroke: #ddcc33; stroke-width: 1; }
|
| 125 |
|
| 126 |
-
/*
|
| 127 |
.acc-label {
|
| 128 |
-
pointer-events: none; font-size:
|
| 129 |
-
stroke: #000; stroke-width:
|
| 130 |
}
|
| 131 |
-
/* Ghost marker for add mode */
|
| 132 |
.ghost-marker {
|
| 133 |
fill: rgba(255, 50, 50, 0.5); stroke: #ff3333; stroke-width: 2.5;
|
| 134 |
stroke-dasharray: 4 2;
|
|
@@ -137,24 +192,28 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 137 |
fill: #ff3333; font-size: 14px; font-weight: bold; opacity: 0.9;
|
| 138 |
}
|
| 139 |
|
| 140 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
#chord-popup {
|
| 142 |
-
position: absolute; background:
|
| 143 |
border-radius: 6px; padding: 6px 0; z-index: 100; min-width: 140px;
|
| 144 |
-
box-shadow:
|
| 145 |
}
|
| 146 |
#chord-popup.hidden { display: none; }
|
| 147 |
-
#chord-popup-title { padding: 4px 12px; font-size: 11px; color:
|
| 148 |
#chord-popup-list { list-style: none; }
|
| 149 |
#chord-popup-list li {
|
| 150 |
padding: 6px 12px; cursor: pointer; font-size: 13px;
|
| 151 |
}
|
| 152 |
-
#chord-popup-list li:hover { background:
|
| 153 |
#chord-popup-list li .voice-dot {
|
| 154 |
display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px;
|
| 155 |
}
|
| 156 |
|
| 157 |
-
/* Barline overlays */
|
| 158 |
.barline-overlay {
|
| 159 |
pointer-events: none;
|
| 160 |
transition: stroke-width 0.1s, opacity 0.1s;
|
|
@@ -167,7 +226,7 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 167 |
.barline-overlay.bl-selected { stroke: #0066ff; stroke-width: 3; opacity: 1.0; }
|
| 168 |
.barline-overlay.bl-hover { stroke-width: 2.5; opacity: 0.8; }
|
| 169 |
|
| 170 |
-
/* Free glyph overlays */
|
| 171 |
.free-glyph-box {
|
| 172 |
fill: rgba(255, 153, 0, 0.15);
|
| 173 |
stroke: #ff9900;
|
|
@@ -186,51 +245,63 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 186 |
fill: rgba(0, 204, 68, 0.15);
|
| 187 |
}
|
| 188 |
|
| 189 |
-
/* Glyph shape popup */
|
| 190 |
#glyph-popup {
|
| 191 |
-
position: fixed; background:
|
| 192 |
border-radius: 8px; padding: 10px 0; z-index: 10000; min-width: 280px;
|
| 193 |
max-height: 70vh; overflow-y: auto;
|
| 194 |
-
box-shadow:
|
| 195 |
}
|
| 196 |
#glyph-popup.hidden { display: none; }
|
| 197 |
-
#glyph-popup-title { padding: 8px 20px; font-size: 20px; color:
|
| 198 |
#glyph-popup-list { list-style: none; }
|
| 199 |
#glyph-popup-list li {
|
| 200 |
padding: 10px 20px; cursor: pointer; font-size: 22px;
|
| 201 |
}
|
| 202 |
-
#glyph-popup-list li:hover { background:
|
| 203 |
#glyph-popup-list .glyph-cat {
|
| 204 |
-
padding: 6px 20px; font-size: 18px; color: #ff9900; pointer-events: none; font-weight: bold; border-top: 1px solid
|
| 205 |
}
|
| 206 |
|
| 207 |
-
/* Ghost barline
|
| 208 |
.ghost-barline {
|
| 209 |
stroke: #ff3333; stroke-width: 1.5; opacity: 0.5;
|
| 210 |
stroke-dasharray: 6 3; pointer-events: none;
|
| 211 |
}
|
| 212 |
-
|
| 213 |
-
/* Barline measure label */
|
| 214 |
.barline-label {
|
| 215 |
fill: #00cc44; font-size: 9px; pointer-events: none; opacity: 0.7;
|
| 216 |
}
|
| 217 |
|
| 218 |
-
/* Barline toolbar */
|
| 219 |
#barline-toolbar {
|
| 220 |
-
display: none;
|
| 221 |
-
|
| 222 |
-
|
|
|
|
|
|
|
| 223 |
}
|
| 224 |
#barline-toolbar.active { display: flex; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
#barline-toolbar button {
|
| 226 |
-
|
| 227 |
-
|
|
|
|
| 228 |
}
|
| 229 |
-
#barline-toolbar button:hover { background:
|
| 230 |
-
#barline-toolbar
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
|
| 233 |
-
/* Rhythm validation warnings */
|
| 234 |
.rhythm-warning {
|
| 235 |
pointer-events: none;
|
| 236 |
animation: rhythm-pulse 2s ease-in-out infinite;
|
|
@@ -240,38 +311,37 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 240 |
50% { fill: rgba(255, 40, 40, 0.25); }
|
| 241 |
}
|
| 242 |
|
| 243 |
-
/* Timeline Panel (Piano Roll) —
|
| 244 |
#timeline-panel {
|
| 245 |
flex: 1; min-width: 0;
|
| 246 |
-
background:
|
| 247 |
display: flex; flex-direction: column; overflow: hidden;
|
| 248 |
}
|
| 249 |
#timeline-header {
|
| 250 |
display: flex; align-items: center; padding: 10px 14px;
|
| 251 |
-
background: linear-gradient(180deg,
|
| 252 |
-
border-bottom:
|
| 253 |
}
|
| 254 |
#timeline-title {
|
| 255 |
-
font-weight: bold; color:
|
| 256 |
-
text-shadow: 0 0 8px rgba(233,69,96,0.4);
|
| 257 |
white-space: nowrap;
|
| 258 |
}
|
| 259 |
#timeline-controls { display: flex; align-items: center; gap: 12px; margin-left: auto; }
|
| 260 |
#timeline-snap-label {
|
| 261 |
-
color:
|
| 262 |
cursor: pointer;
|
| 263 |
}
|
| 264 |
#timeline-snap-label input[type="checkbox"] { width: 20px; height: 20px; }
|
| 265 |
-
#timeline-snap-label:hover { color:
|
| 266 |
#timeline-grid-select {
|
| 267 |
-
background:
|
| 268 |
-
border-radius: 4px; font-size:
|
| 269 |
}
|
| 270 |
#timeline-close {
|
| 271 |
-
background: none; border: none; color:
|
| 272 |
-
font-size:
|
| 273 |
}
|
| 274 |
-
#timeline-close:hover { color:
|
| 275 |
#timeline-body {
|
| 276 |
flex: 1; overflow-y: auto; overflow-x: hidden; padding: 8px;
|
| 277 |
}
|
|
@@ -279,34 +349,34 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 279 |
/* Staff lane */
|
| 280 |
.tl-lane {
|
| 281 |
margin-bottom: 6px; position: relative;
|
| 282 |
-
background: #0a0a16; border: 1px solid
|
| 283 |
overflow: hidden;
|
| 284 |
}
|
| 285 |
.tl-lane-label {
|
| 286 |
position: absolute; left: 0; top: 0; bottom: 0; width: 80px;
|
| 287 |
-
background:
|
| 288 |
-
font-size:
|
| 289 |
pointer-events: none; z-index: 2; line-height: 1.3;
|
| 290 |
display: flex; flex-direction: column; justify-content: center;
|
| 291 |
}
|
| 292 |
-
.tl-lane-label .staff-id { font-size:
|
| 293 |
.tl-grid-line {
|
| 294 |
position: absolute; top: 0; bottom: 0; width: 1px;
|
| 295 |
-
background:
|
| 296 |
}
|
| 297 |
-
.tl-grid-line.beat { background:
|
| 298 |
-
.tl-grid-line.bar-start { background:
|
| 299 |
.tl-grid-label {
|
| 300 |
-
position: absolute; bottom: 1px; font-size: 8px; color:
|
| 301 |
pointer-events: none; transform: translateX(-50%); z-index: 0;
|
| 302 |
}
|
| 303 |
|
| 304 |
-
/* Note block */
|
| 305 |
.tl-note {
|
| 306 |
-
position: absolute; height:
|
| 307 |
border-radius: 4px; cursor: grab; z-index: 1;
|
| 308 |
-
font-size:
|
| 309 |
-
text-align: center; padding: 0 3px;
|
| 310 |
color: #fff; white-space: nowrap; overflow: hidden;
|
| 311 |
text-overflow: ellipsis;
|
| 312 |
user-select: none; min-width: 18px;
|
|
@@ -316,17 +386,17 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 316 |
}
|
| 317 |
.tl-note:hover {
|
| 318 |
box-shadow: 0 0 8px rgba(255,255,255,0.25), inset 0 0 12px rgba(255,255,255,0.08);
|
| 319 |
-
z-index: 3; transform: scaleY(1.
|
| 320 |
}
|
| 321 |
.tl-note.dragging {
|
| 322 |
cursor: grabbing; opacity: 0.9; z-index: 4;
|
| 323 |
-
box-shadow: 0 0 14px rgba(
|
| 324 |
-
transform: scaleY(1.
|
| 325 |
}
|
| 326 |
.tl-note.selected {
|
| 327 |
border: 2px solid #ffd700;
|
| 328 |
-
box-shadow: 0 0
|
| 329 |
-
transform: scaleY(1.
|
| 330 |
}
|
| 331 |
.tl-note.rest {
|
| 332 |
background: #222233 !important; color: #777; border-style: dashed;
|
|
@@ -341,7 +411,7 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 341 |
.tl-note.voice3 { background: linear-gradient(135deg, #228833, #33aa44); }
|
| 342 |
.tl-note.voice4 { background: linear-gradient(135deg, #cc8800, #eea500); }
|
| 343 |
|
| 344 |
-
/* Rubber-band selection */
|
| 345 |
.tl-rubber {
|
| 346 |
border: 2px dashed #ffd700; background: rgba(255,215,0,0.1);
|
| 347 |
pointer-events: none; z-index: 100;
|
|
@@ -361,13 +431,33 @@ body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e
|
|
| 361 |
/* Measure navigation arrows */
|
| 362 |
.tl-nav { display: flex; gap: 8px; margin-bottom: 8px; }
|
| 363 |
.tl-nav button {
|
| 364 |
-
padding: 6px 16px; background:
|
| 365 |
-
border-radius: 4px; cursor: pointer; font-size:
|
| 366 |
}
|
| 367 |
-
.tl-nav button:hover { background:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
|
| 369 |
/* Status Bar */
|
| 370 |
#status-bar {
|
| 371 |
-
display: flex;
|
| 372 |
-
|
| 373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 2 |
+
body { font-family: var(--font-sans); font-size: var(--fs-body); background: var(--bg-1); color: var(--ink-0); display: flex; flex-direction: column; height: 100vh; }
|
| 3 |
|
| 4 |
/* Feedback Banner */
|
| 5 |
+
#feedback-bar { display: none; }
|
|
|
|
|
|
|
|
|
|
| 6 |
#feedback-btn {
|
| 7 |
display: inline-block; padding: 8px 40px;
|
| 8 |
+
background: var(--accent-dim); color: #fff; font-size: var(--fs-body); font-weight: bold;
|
| 9 |
text-decoration: none; border-radius: 6px; letter-spacing: 1px;
|
| 10 |
}
|
| 11 |
+
#feedback-btn:hover { background: #f7b06d; }
|
| 12 |
|
| 13 |
/* Upload Bar */
|
| 14 |
#upload-bar {
|
| 15 |
display: flex; align-items: center; gap: 15px; padding: 8px 12px;
|
| 16 |
+
background: var(--bg-2); border-bottom: 1px solid var(--line);
|
| 17 |
+
font-size: var(--fs-body); flex-wrap: wrap;
|
| 18 |
+
}
|
| 19 |
+
#upload-bar label { display: flex; align-items: center; gap: 6px; font-size: var(--fs-label); }
|
| 20 |
+
#upload-bar input[type="file"] { width: 340px; font-size: var(--fs-label); }
|
| 21 |
+
#upload-bar input[type="number"] { width: 75px; background: var(--bg-3); border: 1px solid var(--line); color: var(--ink-0); padding: 4px 5px; border-radius: 4px; font-size: var(--fs-label); }
|
| 22 |
+
#load-btn { padding: 8px 22px; background: linear-gradient(180deg, #f7b06d, #e08840); color: #1c1004; border: none; border-radius: 5px; cursor: pointer; font-weight: 600; font-size: var(--fs-label); }
|
| 23 |
+
#load-btn:hover { filter: brightness(1.06); }
|
| 24 |
+
#load-status { color: var(--ink-2); font-size: var(--fs-meta); }
|
| 25 |
#page-nav { display: inline-flex; align-items: center; gap: 6px; }
|
| 26 |
+
#page-nav button { padding: 4px 12px; background: var(--bg-3); color: var(--ink-0); border: 1px solid var(--line); border-radius: 4px; cursor: pointer; font-size: var(--fs-label); }
|
| 27 |
+
#page-nav button:hover { background: var(--bg-4); }
|
| 28 |
+
#page-indicator { font-size: var(--fs-label); font-family: var(--font-mono); color: var(--ink-0); min-width: 60px; text-align: center; }
|
| 29 |
|
| 30 |
/* Toolbar */
|
| 31 |
#toolbar {
|
| 32 |
display: flex; align-items: center; gap: 8px; padding: 6px 12px;
|
| 33 |
+
background: var(--bg-3); border-bottom: 1px solid var(--line); flex-wrap: wrap;
|
| 34 |
+
font-size: var(--fs-body);
|
| 35 |
}
|
| 36 |
+
.tool-group { display: flex; align-items: center; gap: 4px; padding: 0 6px; border-right: 1px solid var(--line); }
|
| 37 |
.tool-group:last-child { border-right: none; }
|
| 38 |
.tool-group.right { margin-left: auto; }
|
| 39 |
#toolbar button {
|
| 40 |
+
padding: 6px 11px; background: var(--bg-2); color: var(--ink-0); border: 1px solid var(--line-strong);
|
| 41 |
+
border-radius: 3px; cursor: pointer; font-size: var(--fs-label);
|
| 42 |
+
}
|
| 43 |
+
#toolbar button:hover { background: var(--bg-4); border-color: var(--line-strong); }
|
| 44 |
+
#toolbar button:active { background: var(--accent-soft); color: var(--accent); }
|
| 45 |
+
#btn-show-free-glyphs { padding: 10px 18px; font-size: var(--fs-label); font-weight: bold; background: rgba(255,153,0,.12); border-color: rgba(255,153,0,.4); color: #ffcc88; }
|
| 46 |
+
#btn-show-free-glyphs:hover { background: rgba(255,153,0,.2); }
|
| 47 |
+
#btn-show-free-glyphs.active { background: rgba(255,153,0,.25); border-color: #ff9900; color: #ffd9a3; }
|
| 48 |
+
#btn-download { background: rgba(124,212,84,.12); border-color: rgba(124,212,84,.4); color: #b6e4a4; }
|
| 49 |
+
#btn-download:hover { background: rgba(124,212,84,.2); }
|
| 50 |
+
#feedback-link {
|
| 51 |
+
display: inline-flex; width: 30px; height: 30px;
|
| 52 |
+
align-items: center; justify-content: center;
|
| 53 |
+
color: var(--ink-2); text-decoration: none; border-radius: var(--r-md);
|
| 54 |
+
}
|
| 55 |
+
#feedback-link:hover { background: var(--bg-3); color: var(--ink-0); }
|
| 56 |
+
#toolbar label { font-size: var(--fs-label); }
|
| 57 |
+
#toolbar input[type="number"] { font-size: var(--fs-label); width: 68px; }
|
| 58 |
#toolbar input[type="range"] { width: 112px; height: 12px; }
|
| 59 |
|
| 60 |
#zoom-slider { width: 112px; vertical-align: middle; }
|
| 61 |
+
#zoom-label { font-size: var(--fs-meta); color: var(--ink-2); min-width: 38px; display: inline-block; }
|
| 62 |
|
| 63 |
/* Shortcut Reference Bar */
|
| 64 |
+
#shortcut-bar { display: none; }
|
| 65 |
+
#shortcut-bar b { color: var(--ink-1); }
|
| 66 |
+
|
| 67 |
+
/* OMR Apply Button (moved to status-bar in Phase 3) */
|
| 68 |
+
#omr-apply-btn {
|
| 69 |
+
margin-left: 8px; padding: 4px 10px;
|
| 70 |
+
background: rgba(124,212,84,.12); border: 1px solid rgba(124,212,84,.4); color: #b6e4a4;
|
| 71 |
+
border-radius: 5px; cursor: pointer; font-size: var(--fs-meta);
|
| 72 |
+
}
|
| 73 |
+
#omr-apply-btn:hover { background: rgba(124,212,84,.2); }
|
| 74 |
+
#omr-apply-badge {
|
| 75 |
+
background: var(--danger); color: #fff; border-radius: 8px;
|
| 76 |
+
padding: 0 5px; margin-left: 4px; font-size: 10px;
|
| 77 |
}
|
|
|
|
| 78 |
|
| 79 |
/* Progress Bar */
|
| 80 |
#progress-bar-container {
|
| 81 |
+
position: relative; height: 38px; background: var(--bg-0); border-bottom: 1px solid var(--line);
|
| 82 |
cursor: pointer; user-select: none;
|
| 83 |
}
|
| 84 |
#progress-bar-fill {
|
| 85 |
+
height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent), #f7b06d);
|
| 86 |
transition: none; pointer-events: none;
|
| 87 |
}
|
| 88 |
#progress-time {
|
| 89 |
position: absolute; top: 0; right: 8px; line-height: 38px;
|
| 90 |
+
font-size: var(--fs-meta); color: var(--ink-2); pointer-events: none;
|
| 91 |
}
|
| 92 |
|
| 93 |
+
/* Mode Panel (left aside) */
|
| 94 |
+
#mode-panel {
|
| 95 |
+
width: 248px; flex-shrink: 0;
|
| 96 |
+
background: var(--bg-2); border-right: 1px solid var(--line);
|
| 97 |
+
display: flex; flex-direction: column;
|
| 98 |
+
overflow-y: auto;
|
| 99 |
+
}
|
| 100 |
+
#mode-panel .mode-list { padding: 8px; display: flex; flex-direction: column; gap: 4px; }
|
| 101 |
+
.mode-row {
|
| 102 |
+
display: flex; align-items: center; gap: 10px;
|
| 103 |
+
padding: 8px 10px; border-radius: 6px;
|
| 104 |
+
background: transparent; border: 1px solid transparent;
|
| 105 |
+
color: var(--ink-0); cursor: pointer;
|
| 106 |
+
font-size: var(--fs-label); text-align: left;
|
| 107 |
+
}
|
| 108 |
+
.mode-row .ic {
|
| 109 |
+
width: 24px; height: 24px; display: inline-flex; align-items: center; justify-content: center;
|
| 110 |
+
background: var(--bg-3); border-radius: 5px; color: var(--ink-1); font-size: 13px;
|
| 111 |
+
}
|
| 112 |
+
.mode-row .kbd-chip { margin-left: auto; }
|
| 113 |
+
.mode-row.is-active {
|
| 114 |
+
background: var(--accent-soft); border-color: var(--accent-dim); color: var(--accent);
|
| 115 |
+
}
|
| 116 |
+
.mode-row.is-active .ic { background: rgba(243,162,91,.2); color: var(--accent); }
|
| 117 |
+
|
| 118 |
+
#mode-panel .display-toggles { padding: 4px 14px 12px; display: flex; flex-direction: column; gap: 4px; }
|
| 119 |
+
.tg { display: flex; align-items: center; gap: 10px; font-size: var(--fs-label); color: var(--ink-1); cursor: pointer; padding: 4px 0; }
|
| 120 |
+
.tg input { accent-color: var(--accent); }
|
| 121 |
+
|
| 122 |
+
#mode-panel .offset-grid {
|
| 123 |
+
padding: 6px 14px 12px;
|
| 124 |
+
display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px;
|
| 125 |
+
}
|
| 126 |
+
.offset-grid label { display: flex; flex-direction: column; gap: 4px; font-size: var(--fs-meta); color: var(--ink-2); }
|
| 127 |
+
.offset-grid input[type="number"] {
|
| 128 |
+
background: #0a0d14; border: 1px solid var(--line); border-radius: 5px;
|
| 129 |
+
color: var(--ink-0); padding: 4px 6px; font-family: var(--font-mono); font-size: 11.5px;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
#warning-list { padding: 6px 14px 12px; display: flex; flex-direction: column; gap: 6px; font-size: var(--fs-meta); }
|
| 133 |
+
|
| 134 |
+
/* Main Area: flex row for mode-panel + score + timeline */
|
| 135 |
#main-area {
|
| 136 |
flex: 1; display: flex; overflow: hidden; min-height: 0;
|
| 137 |
}
|
| 138 |
|
| 139 |
/* Main Canvas */
|
| 140 |
#canvas-wrapper {
|
| 141 |
+
flex: 1; overflow: auto; background: var(--bg-0); position: relative; min-width: 0;
|
| 142 |
}
|
| 143 |
#canvas-container {
|
| 144 |
position: relative; display: inline-block;
|
|
|
|
| 151 |
position: absolute; top: 0; left: 0; pointer-events: none;
|
| 152 |
}
|
| 153 |
|
| 154 |
+
/* ═══════════════════════════════════════════════════════════════
|
| 155 |
+
PRESERVED — do not change. See PRESERVED_VALUES.md §1
|
| 156 |
+
═══════════════════════════════════════════════════════════════ */
|
| 157 |
+
|
| 158 |
/* Markers */
|
| 159 |
.marker {
|
| 160 |
pointer-events: all; cursor: pointer;
|
|
|
|
| 179 |
.marker.grade-mid { fill: rgba(255, 165, 0, 0.8); stroke: #ffa500; stroke-width: 1.5; }
|
| 180 |
.marker.grade-ok { fill: rgba(255, 220, 50, 0.7); stroke: #ddcc33; stroke-width: 1; }
|
| 181 |
|
| 182 |
+
/* PRESERVED §2 — Accidental & ghost labels */
|
| 183 |
.acc-label {
|
| 184 |
+
pointer-events: none; font-size: 46px; fill: #ffd700; font-weight: bold;
|
| 185 |
+
stroke: #000; stroke-width: 4px; paint-order: stroke fill;
|
| 186 |
}
|
|
|
|
| 187 |
.ghost-marker {
|
| 188 |
fill: rgba(255, 50, 50, 0.5); stroke: #ff3333; stroke-width: 2.5;
|
| 189 |
stroke-dasharray: 4 2;
|
|
|
|
| 192 |
fill: #ff3333; font-size: 14px; font-weight: bold; opacity: 0.9;
|
| 193 |
}
|
| 194 |
|
| 195 |
+
/* ═══════════════════════════════════════════════════════════════
|
| 196 |
+
CHROME — retokened
|
| 197 |
+
═══════════════════════════════════════════════════════════════ */
|
| 198 |
+
|
| 199 |
+
/* Chord Popup — chrome retokened, position PRESERVED §6 */
|
| 200 |
#chord-popup {
|
| 201 |
+
position: absolute; background: var(--bg-2); border: 1px solid var(--line-strong);
|
| 202 |
border-radius: 6px; padding: 6px 0; z-index: 100; min-width: 140px;
|
| 203 |
+
box-shadow: var(--shadow-md);
|
| 204 |
}
|
| 205 |
#chord-popup.hidden { display: none; }
|
| 206 |
+
#chord-popup-title { padding: 4px 12px; font-size: 11px; color: var(--ink-2); border-bottom: 1px solid var(--line); }
|
| 207 |
#chord-popup-list { list-style: none; }
|
| 208 |
#chord-popup-list li {
|
| 209 |
padding: 6px 12px; cursor: pointer; font-size: 13px;
|
| 210 |
}
|
| 211 |
+
#chord-popup-list li:hover { background: var(--bg-3); }
|
| 212 |
#chord-popup-list li .voice-dot {
|
| 213 |
display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px;
|
| 214 |
}
|
| 215 |
|
| 216 |
+
/* PRESERVED §4 — Barline overlays */
|
| 217 |
.barline-overlay {
|
| 218 |
pointer-events: none;
|
| 219 |
transition: stroke-width 0.1s, opacity 0.1s;
|
|
|
|
| 226 |
.barline-overlay.bl-selected { stroke: #0066ff; stroke-width: 3; opacity: 1.0; }
|
| 227 |
.barline-overlay.bl-hover { stroke-width: 2.5; opacity: 0.8; }
|
| 228 |
|
| 229 |
+
/* PRESERVED §5 — Free glyph overlays */
|
| 230 |
.free-glyph-box {
|
| 231 |
fill: rgba(255, 153, 0, 0.15);
|
| 232 |
stroke: #ff9900;
|
|
|
|
| 245 |
fill: rgba(0, 204, 68, 0.15);
|
| 246 |
}
|
| 247 |
|
| 248 |
+
/* PRESERVED §6 — Glyph shape popup (sizes/position locked, chrome retokened) */
|
| 249 |
#glyph-popup {
|
| 250 |
+
position: fixed; background: var(--bg-2); border: 2px solid #ff9900;
|
| 251 |
border-radius: 8px; padding: 10px 0; z-index: 10000; min-width: 280px;
|
| 252 |
max-height: 70vh; overflow-y: auto;
|
| 253 |
+
box-shadow: var(--shadow-lg);
|
| 254 |
}
|
| 255 |
#glyph-popup.hidden { display: none; }
|
| 256 |
+
#glyph-popup-title { padding: 8px 20px; font-size: 20px; color: var(--ink-1); border-bottom: 1px solid var(--line); font-weight: bold; }
|
| 257 |
#glyph-popup-list { list-style: none; }
|
| 258 |
#glyph-popup-list li {
|
| 259 |
padding: 10px 20px; cursor: pointer; font-size: 22px;
|
| 260 |
}
|
| 261 |
+
#glyph-popup-list li:hover { background: var(--bg-3); }
|
| 262 |
#glyph-popup-list .glyph-cat {
|
| 263 |
+
padding: 6px 20px; font-size: 18px; color: #ff9900; pointer-events: none; font-weight: bold; border-top: 1px solid var(--line);
|
| 264 |
}
|
| 265 |
|
| 266 |
+
/* PRESERVED §4 — Ghost barline & barline label */
|
| 267 |
.ghost-barline {
|
| 268 |
stroke: #ff3333; stroke-width: 1.5; opacity: 0.5;
|
| 269 |
stroke-dasharray: 6 3; pointer-events: none;
|
| 270 |
}
|
|
|
|
|
|
|
| 271 |
.barline-label {
|
| 272 |
fill: #00cc44; font-size: 9px; pointer-events: none; opacity: 0.7;
|
| 273 |
}
|
| 274 |
|
| 275 |
+
/* Barline toolbar — chrome retokened */
|
| 276 |
#barline-toolbar {
|
| 277 |
+
display: none;
|
| 278 |
+
height: 44px; padding: 0 16px; align-items: center; gap: 12px;
|
| 279 |
+
background: linear-gradient(180deg, rgba(255,108,122,.16), rgba(255,108,122,.05));
|
| 280 |
+
border-bottom: 1px solid rgba(255,108,122,.35);
|
| 281 |
+
font-size: var(--fs-label); color: var(--ink-1);
|
| 282 |
}
|
| 283 |
#barline-toolbar.active { display: flex; }
|
| 284 |
+
#barline-toolbar .bl-mode-label {
|
| 285 |
+
padding: 4px 12px; border-radius: 999px;
|
| 286 |
+
font-size: 11px; font-weight: 700; letter-spacing: .06em;
|
| 287 |
+
background: rgba(255,108,122,.25); color: #ffd0d4; border: 1px solid rgba(255,108,122,.4);
|
| 288 |
+
}
|
| 289 |
+
#barline-toolbar #bl-count { font-family: var(--font-mono); color: var(--ink-1); }
|
| 290 |
+
#barline-toolbar .bl-hint { color: var(--ink-2); font-size: var(--fs-meta); margin-left: auto; }
|
| 291 |
#barline-toolbar button {
|
| 292 |
+
height: 28px; padding: 0 12px; border-radius: var(--r-md);
|
| 293 |
+
background: var(--bg-3); border: 1px solid var(--line-strong); color: var(--ink-0);
|
| 294 |
+
font-size: var(--fs-label); cursor: pointer;
|
| 295 |
}
|
| 296 |
+
#barline-toolbar button:hover { background: var(--bg-4); }
|
| 297 |
+
#barline-toolbar #bl-btn-accept {
|
| 298 |
+
background: linear-gradient(180deg, #f7b06d, #e08840); border-color: #bf6c2f; color: #1c1004; font-weight: 600;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
/* Dim markers during barline edit mode */
|
| 302 |
+
body.mode-barline #marker-svg .marker { opacity: 0.3; transition: opacity .15s; }
|
| 303 |
|
| 304 |
+
/* PRESERVED §8 — Rhythm validation warnings */
|
| 305 |
.rhythm-warning {
|
| 306 |
pointer-events: none;
|
| 307 |
animation: rhythm-pulse 2s ease-in-out infinite;
|
|
|
|
| 311 |
50% { fill: rgba(255, 40, 40, 0.25); }
|
| 312 |
}
|
| 313 |
|
| 314 |
+
/* Timeline Panel (Piano Roll) — chrome retokened */
|
| 315 |
#timeline-panel {
|
| 316 |
flex: 1; min-width: 0;
|
| 317 |
+
background: var(--bg-1); border-left: 1px solid var(--line);
|
| 318 |
display: flex; flex-direction: column; overflow: hidden;
|
| 319 |
}
|
| 320 |
#timeline-header {
|
| 321 |
display: flex; align-items: center; padding: 10px 14px;
|
| 322 |
+
background: linear-gradient(180deg, var(--bg-2), var(--bg-1));
|
| 323 |
+
border-bottom: 1px solid var(--line); gap: 10px; flex-shrink: 0;
|
| 324 |
}
|
| 325 |
#timeline-title {
|
| 326 |
+
font-weight: bold; color: var(--ink-0); font-size: var(--fs-display);
|
|
|
|
| 327 |
white-space: nowrap;
|
| 328 |
}
|
| 329 |
#timeline-controls { display: flex; align-items: center; gap: 12px; margin-left: auto; }
|
| 330 |
#timeline-snap-label {
|
| 331 |
+
color: var(--ink-1); font-size: var(--fs-label); display: flex; align-items: center; gap: 6px;
|
| 332 |
cursor: pointer;
|
| 333 |
}
|
| 334 |
#timeline-snap-label input[type="checkbox"] { width: 20px; height: 20px; }
|
| 335 |
+
#timeline-snap-label:hover { color: var(--ink-0); }
|
| 336 |
#timeline-grid-select {
|
| 337 |
+
background: var(--bg-3); color: var(--ink-0); border: 1px solid var(--line);
|
| 338 |
+
border-radius: 4px; font-size: var(--fs-label); padding: 6px 10px;
|
| 339 |
}
|
| 340 |
#timeline-close {
|
| 341 |
+
background: none; border: none; color: var(--ink-3);
|
| 342 |
+
font-size: 20px; cursor: pointer; padding: 0 8px; line-height: 1;
|
| 343 |
}
|
| 344 |
+
#timeline-close:hover { color: var(--accent); }
|
| 345 |
#timeline-body {
|
| 346 |
flex: 1; overflow-y: auto; overflow-x: hidden; padding: 8px;
|
| 347 |
}
|
|
|
|
| 349 |
/* Staff lane */
|
| 350 |
.tl-lane {
|
| 351 |
margin-bottom: 6px; position: relative;
|
| 352 |
+
background: #0a0a16; border: 1px solid var(--line); border-radius: 4px;
|
| 353 |
overflow: hidden;
|
| 354 |
}
|
| 355 |
.tl-lane-label {
|
| 356 |
position: absolute; left: 0; top: 0; bottom: 0; width: 80px;
|
| 357 |
+
background: var(--bg-2); border-right: 1px solid var(--line);
|
| 358 |
+
font-size: var(--fs-meta); color: var(--ink-2); padding: 4px 6px;
|
| 359 |
pointer-events: none; z-index: 2; line-height: 1.3;
|
| 360 |
display: flex; flex-direction: column; justify-content: center;
|
| 361 |
}
|
| 362 |
+
.tl-lane-label .staff-id { font-size: var(--fs-label); color: var(--ink-1); font-weight: 700; }
|
| 363 |
.tl-grid-line {
|
| 364 |
position: absolute; top: 0; bottom: 0; width: 1px;
|
| 365 |
+
background: var(--bg-1); pointer-events: none; z-index: 0;
|
| 366 |
}
|
| 367 |
+
.tl-grid-line.beat { background: var(--line); }
|
| 368 |
+
.tl-grid-line.bar-start { background: var(--line-strong); width: 2px; }
|
| 369 |
.tl-grid-label {
|
| 370 |
+
position: absolute; bottom: 1px; font-size: 8px; color: var(--ink-3);
|
| 371 |
pointer-events: none; transform: translateX(-50%); z-index: 0;
|
| 372 |
}
|
| 373 |
|
| 374 |
+
/* PRESERVED §7 — Note block (height/font-size changeable, voice gradients locked) */
|
| 375 |
.tl-note {
|
| 376 |
+
position: absolute; height: 56px;
|
| 377 |
border-radius: 4px; cursor: grab; z-index: 1;
|
| 378 |
+
font-size: var(--fs-label); font-weight: 600; line-height: 52px;
|
| 379 |
+
text-align: center; padding: 0 3px; margin: 7px 0;
|
| 380 |
color: #fff; white-space: nowrap; overflow: hidden;
|
| 381 |
text-overflow: ellipsis;
|
| 382 |
user-select: none; min-width: 18px;
|
|
|
|
| 386 |
}
|
| 387 |
.tl-note:hover {
|
| 388 |
box-shadow: 0 0 8px rgba(255,255,255,0.25), inset 0 0 12px rgba(255,255,255,0.08);
|
| 389 |
+
z-index: 3; transform: scaleY(1.15);
|
| 390 |
}
|
| 391 |
.tl-note.dragging {
|
| 392 |
cursor: grabbing; opacity: 0.9; z-index: 4;
|
| 393 |
+
box-shadow: 0 0 14px rgba(243,162,91,0.7), 0 2px 8px rgba(0,0,0,0.5);
|
| 394 |
+
transform: scaleY(1.2);
|
| 395 |
}
|
| 396 |
.tl-note.selected {
|
| 397 |
border: 2px solid #ffd700;
|
| 398 |
+
box-shadow: 0 0 8px rgba(255,215,0,0.7), 0 0 16px rgba(255,215,0,0.35);
|
| 399 |
+
transform: scaleY(1.18);
|
| 400 |
}
|
| 401 |
.tl-note.rest {
|
| 402 |
background: #222233 !important; color: #777; border-style: dashed;
|
|
|
|
| 411 |
.tl-note.voice3 { background: linear-gradient(135deg, #228833, #33aa44); }
|
| 412 |
.tl-note.voice4 { background: linear-gradient(135deg, #cc8800, #eea500); }
|
| 413 |
|
| 414 |
+
/* PRESERVED §3 — Rubber-band selection */
|
| 415 |
.tl-rubber {
|
| 416 |
border: 2px dashed #ffd700; background: rgba(255,215,0,0.1);
|
| 417 |
pointer-events: none; z-index: 100;
|
|
|
|
| 431 |
/* Measure navigation arrows */
|
| 432 |
.tl-nav { display: flex; gap: 8px; margin-bottom: 8px; }
|
| 433 |
.tl-nav button {
|
| 434 |
+
padding: 6px 16px; background: var(--bg-2); color: var(--ink-2); border: 1px solid var(--line);
|
| 435 |
+
border-radius: 4px; cursor: pointer; font-size: var(--fs-label);
|
| 436 |
}
|
| 437 |
+
.tl-nav button:hover { background: var(--bg-4); color: var(--ink-0); }
|
| 438 |
+
|
| 439 |
+
/* Multi-Edit Panel */
|
| 440 |
+
#multi-edit-panel {
|
| 441 |
+
width: 280px; flex-shrink: 0;
|
| 442 |
+
background: var(--bg-2); border-left: 1px solid var(--line);
|
| 443 |
+
display: flex; flex-direction: column;
|
| 444 |
+
padding-bottom: 16px; overflow-y: auto;
|
| 445 |
+
}
|
| 446 |
+
#multi-edit-panel[hidden] { display: none; }
|
| 447 |
+
.me-block { padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; border-bottom: 1px solid var(--line); }
|
| 448 |
+
.me-block:last-of-type { border-bottom: 0; flex-direction: row; gap: 8px; }
|
| 449 |
+
.me-label { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-2); }
|
| 450 |
+
.me-row { display: flex; gap: 6px; flex-wrap: wrap; }
|
| 451 |
+
.me-row .btn { flex: 1; min-width: 0; }
|
| 452 |
|
| 453 |
/* Status Bar */
|
| 454 |
#status-bar {
|
| 455 |
+
display: flex; align-items: center; gap: 18px;
|
| 456 |
+
padding: 0 14px; height: 30px;
|
| 457 |
+
background: var(--bg-2); border-top: 1px solid var(--line);
|
| 458 |
+
font-size: var(--fs-meta); color: var(--ink-2);
|
| 459 |
+
flex-shrink: 0;
|
| 460 |
+
}
|
| 461 |
+
#status-mode { color: var(--accent); font-weight: 600; }
|
| 462 |
+
#status-selection { color: var(--ink-1); }
|
| 463 |
+
#status-total { margin-left: auto; }
|
|
@@ -73,15 +73,8 @@
|
|
| 73 |
<div class="tool-group">
|
| 74 |
<label><span data-i18n="zoom">확대</span>: <input type="range" id="zoom-slider" min="25" max="200" value="50" step="5"><span id="zoom-label">50%</span></label>
|
| 75 |
</div>
|
| 76 |
-
<
|
| 77 |
-
|
| 78 |
-
<label><span data-i18n="y_off">Y 오프셋</span>: <input type="number" id="offset-y" value="0" step="1" style="width:84px"></label>
|
| 79 |
-
</div>
|
| 80 |
-
<div class="tool-group">
|
| 81 |
-
<label><span data-i18n="staff_dist">보표 간격</span>: <input type="number" id="staff-dist-input" value="65" step="1" style="width:84px"></label>
|
| 82 |
-
<label><span data-i18n="sys_dist">단 간격 보정</span>: <input type="number" id="sys-dist-adj" value="0" step="1" style="width:84px"></label>
|
| 83 |
-
</div>
|
| 84 |
-
<div class="tool-group">
|
| 85 |
<button id="btn-debug-lines" data-i18n="staff_lines" title="오선 표시/숨김">오선 표시</button>
|
| 86 |
<button id="btn-adjust-staves" data-i18n="adjust_staves" title="오선 위치 수동 조정">오선 조정</button>
|
| 87 |
<button id="btn-show-barlines" data-i18n="show_barlines" title="감지된 마디선 표시/숨김">마디선</button>
|
|
@@ -92,6 +85,7 @@
|
|
| 92 |
<button id="btn-lang" title="한국어/English">EN</button>
|
| 93 |
</div>
|
| 94 |
<div class="tool-group right">
|
|
|
|
| 95 |
<button id="btn-download" data-i18n="download">XML 다운로드</button>
|
| 96 |
</div>
|
| 97 |
</div>
|
|
@@ -124,9 +118,6 @@
|
|
| 124 |
<span><b>Shift+K:</b> <span data-i18n="sc_keysig">조표</span></span>
|
| 125 |
<span><b>Shift+C:</b> <span data-i18n="sc_clef">음자리표</span></span>
|
| 126 |
<span><b>t:</b> Timeline</span>
|
| 127 |
-
<button id="omr-apply-btn" style="margin-left:12px;padding:2px 10px;background:#2a6;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:12px;display:none" onclick="applyOmrEdits()">
|
| 128 |
-
Apply to OMR <span id="omr-apply-badge" style="background:#e44;color:#fff;border-radius:8px;padding:0 5px;margin-left:4px;font-size:10px;display:none"></span>
|
| 129 |
-
</button>
|
| 130 |
</div>
|
| 131 |
|
| 132 |
<!-- Barline Edit Toolbar -->
|
|
@@ -141,6 +132,34 @@
|
|
| 141 |
|
| 142 |
<!-- Main Area: Score + Timeline side by side -->
|
| 143 |
<div id="main-area">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
<div id="canvas-wrapper">
|
| 145 |
<div id="canvas-container">
|
| 146 |
<img id="score-image" alt="Score image">
|
|
@@ -165,6 +184,45 @@
|
|
| 165 |
</div>
|
| 166 |
<div id="timeline-body"></div>
|
| 167 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
</div>
|
| 169 |
|
| 170 |
<!-- Chord Popup -->
|
|
@@ -181,11 +239,60 @@
|
|
| 181 |
|
| 182 |
<!-- Status Bar -->
|
| 183 |
<div id="status-bar">
|
| 184 |
-
<span id="status-mode"
|
| 185 |
<span id="status-selection" data-i18n="no_sel">선택 없음</span>
|
| 186 |
<span id="status-total"></span>
|
|
|
|
|
|
|
|
|
|
| 187 |
</div>
|
| 188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
<script src="/static/corrector.js"></script>
|
| 190 |
</body>
|
| 191 |
</html>
|
|
|
|
| 73 |
<div class="tool-group">
|
| 74 |
<label><span data-i18n="zoom">확대</span>: <input type="range" id="zoom-slider" min="25" max="200" value="50" step="5"><span id="zoom-label">50%</span></label>
|
| 75 |
</div>
|
| 76 |
+
<!-- Hidden buttons: IDs required by corrector.js, proxied from #mode-panel -->
|
| 77 |
+
<div class="tool-group" style="display:none">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
<button id="btn-debug-lines" data-i18n="staff_lines" title="오선 표시/숨김">오선 표시</button>
|
| 79 |
<button id="btn-adjust-staves" data-i18n="adjust_staves" title="오선 위치 수동 조정">오선 조정</button>
|
| 80 |
<button id="btn-show-barlines" data-i18n="show_barlines" title="감지된 마디선 표시/숨김">마디선</button>
|
|
|
|
| 85 |
<button id="btn-lang" title="한국어/English">EN</button>
|
| 86 |
</div>
|
| 87 |
<div class="tool-group right">
|
| 88 |
+
<a id="feedback-link" href="https://docs.google.com/forms/d/e/1FAIpQLScDoM53RMjDLlftORYHXmZ5kmkN4TTZOIyFIRuVsZhp4RGjEA/viewform" target="_blank" rel="noopener" title="피드백">✉</a>
|
| 89 |
<button id="btn-download" data-i18n="download">XML 다운로드</button>
|
| 90 |
</div>
|
| 91 |
</div>
|
|
|
|
| 118 |
<span><b>Shift+K:</b> <span data-i18n="sc_keysig">조표</span></span>
|
| 119 |
<span><b>Shift+C:</b> <span data-i18n="sc_clef">음자리표</span></span>
|
| 120 |
<span><b>t:</b> Timeline</span>
|
|
|
|
|
|
|
|
|
|
| 121 |
</div>
|
| 122 |
|
| 123 |
<!-- Barline Edit Toolbar -->
|
|
|
|
| 132 |
|
| 133 |
<!-- Main Area: Score + Timeline side by side -->
|
| 134 |
<div id="main-area">
|
| 135 |
+
<aside id="mode-panel">
|
| 136 |
+
<section class="panel-head"><h4>모드</h4></section>
|
| 137 |
+
<div class="mode-list">
|
| 138 |
+
<button class="mode-row is-active" data-proxy=""><span class="ic">✎</span> 음표 편집 <span class="kbd-chip">esc</span></button>
|
| 139 |
+
<button class="mode-row" data-proxy="btn-barline-mode"><span class="ic">┊</span> 마디선 편집 <span class="kbd-chip">B</span></button>
|
| 140 |
+
<button class="mode-row" data-proxy="btn-show-free-glyphs"><span class="ic">◇</span> 후보 기호 <span class="kbd-chip" id="glyph-count"></span></button>
|
| 141 |
+
<button class="mode-row" data-proxy="btn-adjust-staves"><span class="ic">≣</span> 오선 위치 조정</button>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<section class="panel-head"><h4>표시</h4></section>
|
| 145 |
+
<div class="display-toggles">
|
| 146 |
+
<label class="tg"><input type="checkbox" data-proxy="btn-debug-lines"> 오선 표시</label>
|
| 147 |
+
<label class="tg"><input type="checkbox" data-proxy="btn-show-barlines"> 마디선 오버레이</label>
|
| 148 |
+
</div>
|
| 149 |
+
|
| 150 |
+
<section class="panel-head"><h4>오프셋 / 간격</h4></section>
|
| 151 |
+
<div class="offset-grid">
|
| 152 |
+
<label>X<input type="number" id="offset-x" value="0" step="1"></label>
|
| 153 |
+
<label>Y<input type="number" id="offset-y" value="0" step="1"></label>
|
| 154 |
+
<label>보표 간격<input type="number" id="staff-dist-input" value="65" step="1"></label>
|
| 155 |
+
<label>단 간격 보정<input type="number" id="sys-dist-adj" value="0" step="1"></label>
|
| 156 |
+
</div>
|
| 157 |
+
|
| 158 |
+
<div style="flex:1"></div>
|
| 159 |
+
<section class="panel-head"><h4>경고</h4></section>
|
| 160 |
+
<div id="warning-list"></div>
|
| 161 |
+
</aside>
|
| 162 |
+
|
| 163 |
<div id="canvas-wrapper">
|
| 164 |
<div id="canvas-container">
|
| 165 |
<img id="score-image" alt="Score image">
|
|
|
|
| 184 |
</div>
|
| 185 |
<div id="timeline-body"></div>
|
| 186 |
</div>
|
| 187 |
+
|
| 188 |
+
<!-- Multi-Edit Panel (appears on multi-select) -->
|
| 189 |
+
<aside id="multi-edit-panel" hidden>
|
| 190 |
+
<section class="panel-head"><h4>다중 편집 <span id="multi-edit-count" class="mono"></span></h4></section>
|
| 191 |
+
<div class="me-block">
|
| 192 |
+
<div class="me-label">음높이</div>
|
| 193 |
+
<div class="me-row">
|
| 194 |
+
<button class="btn" data-proxy="btn-down" data-proxy-shift>▼ 옥타브</button>
|
| 195 |
+
<button class="btn" data-proxy="btn-down">▼ 반음</button>
|
| 196 |
+
<button class="btn" data-proxy="btn-up">반음 ▲</button>
|
| 197 |
+
<button class="btn" data-proxy="btn-up" data-proxy-shift>옥타브 ▲</button>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
<div class="me-block">
|
| 201 |
+
<div class="me-label">길이</div>
|
| 202 |
+
<div class="me-row">
|
| 203 |
+
<button class="btn" data-proxy="btn-dur-whole">𝅝</button>
|
| 204 |
+
<button class="btn" data-proxy="btn-dur-half">𝅗𝅥</button>
|
| 205 |
+
<button class="btn" data-proxy="btn-dur-quarter">𝅘𝅥</button>
|
| 206 |
+
<button class="btn" data-proxy="btn-dur-eighth">𝅘𝅥𝅮</button>
|
| 207 |
+
<button class="btn" data-proxy="btn-dur-16th">𝅘𝅥𝅯</button>
|
| 208 |
+
<button class="btn" data-proxy="btn-dur-32nd">𝅘𝅥𝅰</button>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
<div class="me-block">
|
| 212 |
+
<div class="me-label">임시표</div>
|
| 213 |
+
<div class="me-row">
|
| 214 |
+
<button class="btn" data-proxy="btn-dblsharp">𝄪</button>
|
| 215 |
+
<button class="btn" data-proxy="btn-sharp">♯</button>
|
| 216 |
+
<button class="btn" data-proxy="btn-natural">♮</button>
|
| 217 |
+
<button class="btn" data-proxy="btn-flat">♭</button>
|
| 218 |
+
<button class="btn" data-proxy="btn-dblflat">𝄫</button>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
<div class="me-block">
|
| 222 |
+
<button class="btn ghost" data-proxy="btn-rest-toggle" style="flex:1">음표 ↔ 쉼표</button>
|
| 223 |
+
<button class="btn danger" data-proxy="btn-delete" style="flex:1">🗑 일괄 삭제</button>
|
| 224 |
+
</div>
|
| 225 |
+
</aside>
|
| 226 |
</div>
|
| 227 |
|
| 228 |
<!-- Chord Popup -->
|
|
|
|
| 239 |
|
| 240 |
<!-- Status Bar -->
|
| 241 |
<div id="status-bar">
|
| 242 |
+
<span id="status-mode"></span>
|
| 243 |
<span id="status-selection" data-i18n="no_sel">선택 없음</span>
|
| 244 |
<span id="status-total"></span>
|
| 245 |
+
<button id="omr-apply-btn" onclick="applyOmrEdits()" style="display:none">
|
| 246 |
+
Apply to OMR <span id="omr-apply-badge" style="display:none"></span>
|
| 247 |
+
</button>
|
| 248 |
</div>
|
| 249 |
|
| 250 |
+
<script>
|
| 251 |
+
// Mode panel proxies — clicks the underlying button so corrector.js stays unaware.
|
| 252 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 253 |
+
document.querySelectorAll('[data-proxy]').forEach(el => {
|
| 254 |
+
const targetId = el.dataset.proxy;
|
| 255 |
+
if (!targetId) return;
|
| 256 |
+
const target = document.getElementById(targetId);
|
| 257 |
+
if (!target) return;
|
| 258 |
+
if (el.tagName === 'INPUT') {
|
| 259 |
+
el.addEventListener('change', () => { target.click(); });
|
| 260 |
+
} else {
|
| 261 |
+
el.addEventListener('click', () => { target.click(); });
|
| 262 |
+
}
|
| 263 |
+
});
|
| 264 |
+
|
| 265 |
+
// Barline mode: mirror .active onto body for marker dimming
|
| 266 |
+
const _btb = document.getElementById('barline-toolbar');
|
| 267 |
+
if (_btb) {
|
| 268 |
+
new MutationObserver(() => {
|
| 269 |
+
document.body.classList.toggle('mode-barline', _btb.classList.contains('active'));
|
| 270 |
+
}).observe(_btb, { attributes: true, attributeFilter: ['class'] });
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
// Multi-edit panel: show/hide based on multi-select count
|
| 274 |
+
const _mep = document.getElementById('multi-edit-panel');
|
| 275 |
+
const _mec = document.getElementById('multi-edit-count');
|
| 276 |
+
if (_mep) {
|
| 277 |
+
new MutationObserver(() => {
|
| 278 |
+
const n = document.querySelectorAll('#marker-svg .marker.multi-selected').length;
|
| 279 |
+
const show = n > 1;
|
| 280 |
+
_mep.toggleAttribute('hidden', !show);
|
| 281 |
+
if (show) _mec.textContent = '(' + n + ')';
|
| 282 |
+
}).observe(document.getElementById('marker-svg'), { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
// Shift-proxy for octave step buttons in multi-edit panel
|
| 286 |
+
document.querySelectorAll('[data-proxy][data-proxy-shift]').forEach(el => {
|
| 287 |
+
el.addEventListener('click', e => {
|
| 288 |
+
e.preventDefault(); e.stopPropagation();
|
| 289 |
+
const target = document.getElementById(el.dataset.proxy);
|
| 290 |
+
if (!target) return;
|
| 291 |
+
target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, shiftKey: true }));
|
| 292 |
+
}, true);
|
| 293 |
+
});
|
| 294 |
+
});
|
| 295 |
+
</script>
|
| 296 |
<script src="/static/corrector.js"></script>
|
| 297 |
</body>
|
| 298 |
</html>
|
|
@@ -1510,21 +1510,36 @@ function matchOmrGrades(notes, omrData) {
|
|
| 1510 |
|
| 1511 |
const _GLYPH_SHAPES = [
|
| 1512 |
{ cat: "Notes", cat_ko: "음표", items: [
|
| 1513 |
-
{ shape: "NOTEHEAD_BLACK", label: "
|
| 1514 |
-
{ shape: "NOTEHEAD_VOID", label: "
|
| 1515 |
-
{ shape: "WHOLE_NOTE", label: "
|
| 1516 |
]},
|
| 1517 |
{ cat: "Rests", cat_ko: "쉼표", items: [
|
| 1518 |
{ shape: "QUARTER_REST", label: "𝄾 Quarter", label_ko: "𝄾 4분쉼표" },
|
| 1519 |
-
{ shape: "EIGHTH_REST", label: "
|
| 1520 |
-
{ shape: "HALF_REST", label: "
|
| 1521 |
-
{ shape: "WHOLE_REST", label: "
|
| 1522 |
]},
|
| 1523 |
{ cat: "Accidentals", cat_ko: "임시표", items: [
|
| 1524 |
{ shape: "SHARP", label: "♯ Sharp", label_ko: "♯ 올림표" },
|
| 1525 |
{ shape: "FLAT", label: "♭ Flat", label_ko: "♭ 내림표" },
|
| 1526 |
{ shape: "NATURAL", label: "♮ Natural", label_ko: "♮ 제자리표" },
|
| 1527 |
]},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1528 |
];
|
| 1529 |
|
| 1530 |
function toggleFreeGlyphs() {
|
|
@@ -1576,6 +1591,19 @@ function openGlyphAssignPopup(fgIdx, event) {
|
|
| 1576 |
|
| 1577 |
const g = freeGlyphData[fgIdx];
|
| 1578 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1579 |
const isKo = currentLang === "ko";
|
| 1580 |
for (const cat of _GLYPH_SHAPES) {
|
| 1581 |
const catLi = document.createElement("li");
|
|
@@ -3153,8 +3181,8 @@ function renderMarkers(notes) {
|
|
| 3153 |
// Accidental label
|
| 3154 |
if (n.alter !== 0) {
|
| 3155 |
const txt = document.createElementNS(SVG_NS, "text");
|
| 3156 |
-
txt.setAttribute("x", n.px +
|
| 3157 |
-
txt.setAttribute("y", n.py +
|
| 3158 |
txt.classList.add("acc-label");
|
| 3159 |
txt.dataset.idx = idx;
|
| 3160 |
txt.textContent = alterStr(n.alter);
|
|
@@ -3450,8 +3478,8 @@ function updateSingleMarker(idx) {
|
|
| 3450 |
accLabel.dataset.idx = idx;
|
| 3451 |
markerSvg.appendChild(accLabel);
|
| 3452 |
}
|
| 3453 |
-
accLabel.setAttribute("x", n.px +
|
| 3454 |
-
accLabel.setAttribute("y", n.py +
|
| 3455 |
accLabel.textContent = accStr;
|
| 3456 |
} else if (accLabel) {
|
| 3457 |
accLabel.remove();
|
|
@@ -3897,6 +3925,7 @@ function raiseNote(skipUndo) {
|
|
| 3897 |
newPitch: stepOctaveToOmrPitch(n.step, n.octave, n.clef.sign) });
|
| 3898 |
}
|
| 3899 |
recomputeAndUpdate(selectedIdx);
|
|
|
|
| 3900 |
}
|
| 3901 |
|
| 3902 |
function lowerNote(skipUndo) {
|
|
@@ -3930,6 +3959,7 @@ function lowerNote(skipUndo) {
|
|
| 3930 |
newPitch: stepOctaveToOmrPitch(n.step, n.octave, n.clef.sign) });
|
| 3931 |
}
|
| 3932 |
recomputeAndUpdate(selectedIdx);
|
|
|
|
| 3933 |
}
|
| 3934 |
|
| 3935 |
function setAccidental(alterValue, skipUndo) {
|
|
@@ -3965,6 +3995,7 @@ function setAccidental(alterValue, skipUndo) {
|
|
| 3965 |
}
|
| 3966 |
}
|
| 3967 |
recomputeAndUpdate(selectedIdx);
|
|
|
|
| 3968 |
}
|
| 3969 |
|
| 3970 |
// ── Note Deletion ────────────────────────────────────────────
|
|
@@ -6424,6 +6455,16 @@ async function loadPage(pageIdx) {
|
|
| 6424 |
markerSvg.setAttribute("width", scoreImage.naturalWidth);
|
| 6425 |
markerSvg.setAttribute("height", scoreImage.naturalHeight);
|
| 6426 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6427 |
renderMarkers(noteInfos);
|
| 6428 |
if (selectedIdx >= 0 && selectedIdx < noteInfos.length) selectNote(selectedIdx);
|
| 6429 |
else { selectedIdx = -1; statusSel.textContent = t("no_sel"); }
|
|
@@ -6473,6 +6514,16 @@ async function loadPage(pageIdx) {
|
|
| 6473 |
markerSvg.setAttribute("width", scoreImage.naturalWidth);
|
| 6474 |
markerSvg.setAttribute("height", scoreImage.naturalHeight);
|
| 6475 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6476 |
// Parse XML — from pre-loaded text or from file
|
| 6477 |
let xmlText;
|
| 6478 |
if (pg.xmlText) {
|
|
@@ -7186,6 +7237,13 @@ let isPlaying = false;
|
|
| 7186 |
let playbackStartOffset = 0; // seconds offset for seek
|
| 7187 |
let cursorSeekTime = 0; // time in seconds where the red cursor is parked
|
| 7188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7189 |
function noteToFreq(step, octave, alter) {
|
| 7190 |
const midi = (octave + 1) * 12 + [0,2,4,5,7,9,11][STEP_INDEX[step]] + alter;
|
| 7191 |
return 440 * Math.pow(2, (midi - 69) / 12);
|
|
@@ -8416,6 +8474,7 @@ document.addEventListener("mouseup", () => {
|
|
| 8416 |
recordOmrEdit({ type: "change_pitch", headId: n.omrHeadId,
|
| 8417 |
newPitch: stepOctaveToOmrPitch(n.step, n.octave, n.clef.sign) });
|
| 8418 |
}
|
|
|
|
| 8419 |
}
|
| 8420 |
if (dragIsXMode) {
|
| 8421 |
document.body.style.cursor = "";
|
|
@@ -8448,7 +8507,7 @@ function recomputeWithAnchorsRealtime() {
|
|
| 8448 |
}
|
| 8449 |
const accLabel = markerSvg.querySelector(`text.acc-label[data-idx="${idx}"]`);
|
| 8450 |
if (accLabel) {
|
| 8451 |
-
accLabel.setAttribute("x", n.px +
|
| 8452 |
}
|
| 8453 |
});
|
| 8454 |
}
|
|
@@ -8786,6 +8845,27 @@ document.addEventListener("mousemove", (e) => {
|
|
| 8786 |
|
| 8787 |
document.addEventListener("mouseup", (e) => {
|
| 8788 |
if (staffDragIdx < 0 || !staffAdjustMode) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8789 |
staffDragIdx = -1;
|
| 8790 |
staffDragOrigData = null;
|
| 8791 |
staffDragDebugLines = [];
|
|
@@ -9682,7 +9762,7 @@ document.addEventListener("mousemove", (e) => {
|
|
| 9682 |
const circle = markerSvg.querySelector(`circle.marker[data-idx="${idx}"]`);
|
| 9683 |
if (circle) circle.setAttribute("cx", n.px);
|
| 9684 |
const accLabel = markerSvg.querySelector(`text.acc-label[data-idx="${idx}"]`);
|
| 9685 |
-
if (accLabel) accLabel.setAttribute("x", n.px +
|
| 9686 |
});
|
| 9687 |
return;
|
| 9688 |
}
|
|
|
|
| 1510 |
|
| 1511 |
const _GLYPH_SHAPES = [
|
| 1512 |
{ cat: "Notes", cat_ko: "음표", items: [
|
| 1513 |
+
{ shape: "NOTEHEAD_BLACK", label: "● Black (filled)", label_ko: "● 검은 음표머리" },
|
| 1514 |
+
{ shape: "NOTEHEAD_VOID", label: "○ Void (half)", label_ko: "○ 빈 음표머리 (2분)" },
|
| 1515 |
+
{ shape: "WHOLE_NOTE", label: "◎ Whole", label_ko: "◎ 온음표" },
|
| 1516 |
]},
|
| 1517 |
{ cat: "Rests", cat_ko: "쉼표", items: [
|
| 1518 |
{ shape: "QUARTER_REST", label: "𝄾 Quarter", label_ko: "𝄾 4분쉼표" },
|
| 1519 |
+
{ shape: "EIGHTH_REST", label: "𝄿 Eighth", label_ko: "𝄿 8분쉼표" },
|
| 1520 |
+
{ shape: "HALF_REST", label: "▬ Half", label_ko: "▬ 2분쉼표" },
|
| 1521 |
+
{ shape: "WHOLE_REST", label: "▄ Whole", label_ko: "▄ 온쉼표" },
|
| 1522 |
]},
|
| 1523 |
{ cat: "Accidentals", cat_ko: "임시표", items: [
|
| 1524 |
{ shape: "SHARP", label: "♯ Sharp", label_ko: "♯ 올림표" },
|
| 1525 |
{ shape: "FLAT", label: "♭ Flat", label_ko: "♭ 내림표" },
|
| 1526 |
{ shape: "NATURAL", label: "♮ Natural", label_ko: "♮ 제자리표" },
|
| 1527 |
]},
|
| 1528 |
+
// { cat: "Clefs", cat_ko: "음자리표", items: [
|
| 1529 |
+
// { shape: "G_CLEF", label: "𝄞 Treble", label_ko: "𝄞 높은음자리표" },
|
| 1530 |
+
// { shape: "F_CLEF", label: "𝄢 Bass", label_ko: "𝄢 낮은음자리표" },
|
| 1531 |
+
// { shape: "C_CLEF", label: "𝄡 Alto/Tenor", label_ko: "𝄡 가온음자리표" },
|
| 1532 |
+
// ]},
|
| 1533 |
+
// { cat: "Dynamics", cat_ko: "셈여림", items: [
|
| 1534 |
+
// { shape: "DYNAMICS_F", label: "f Forte", label_ko: "f 포르테" },
|
| 1535 |
+
// { shape: "DYNAMICS_P", label: "p Piano", label_ko: "p 피아노" },
|
| 1536 |
+
// ]},
|
| 1537 |
+
// { cat: "Other", cat_ko: "기타", items: [
|
| 1538 |
+
// { shape: "FERMATA", label: "𝄐 Fermata", label_ko: "𝄐 늘임표" },
|
| 1539 |
+
// { shape: "FLAG_8TH", label: "⚑ 8th flag", label_ko: "⚑ 8분 꼬리" },
|
| 1540 |
+
// { shape: "FLAG_16TH", label: "⚑⚑ 16th flag", label_ko: "⚑⚑ 16분 꼬리" },
|
| 1541 |
+
// { shape: "DOT", label: "• Dot", label_ko: "• 점" },
|
| 1542 |
+
// ]},
|
| 1543 |
];
|
| 1544 |
|
| 1545 |
function toggleFreeGlyphs() {
|
|
|
|
| 1591 |
|
| 1592 |
const g = freeGlyphData[fgIdx];
|
| 1593 |
|
| 1594 |
+
// Navigate timeline to the measure containing this glyph
|
| 1595 |
+
const glyphCx = g.x + g.w / 2;
|
| 1596 |
+
let closestIdx = -1, closestDist = Infinity;
|
| 1597 |
+
noteInfos.forEach((n, i) => {
|
| 1598 |
+
if (n.systemIdx !== g.systemIdx) return;
|
| 1599 |
+
const d = Math.abs(n.px - glyphCx);
|
| 1600 |
+
if (d < closestDist) { closestDist = d; closestIdx = i; }
|
| 1601 |
+
});
|
| 1602 |
+
if (closestIdx >= 0) {
|
| 1603 |
+
const n = noteInfos[closestIdx];
|
| 1604 |
+
renderTimelinePanel(n.measureNum, n.systemIdx);
|
| 1605 |
+
}
|
| 1606 |
+
|
| 1607 |
const isKo = currentLang === "ko";
|
| 1608 |
for (const cat of _GLYPH_SHAPES) {
|
| 1609 |
const catLi = document.createElement("li");
|
|
|
|
| 3181 |
// Accidental label
|
| 3182 |
if (n.alter !== 0) {
|
| 3183 |
const txt = document.createElementNS(SVG_NS, "text");
|
| 3184 |
+
txt.setAttribute("x", n.px + 8);
|
| 3185 |
+
txt.setAttribute("y", n.py + 5);
|
| 3186 |
txt.classList.add("acc-label");
|
| 3187 |
txt.dataset.idx = idx;
|
| 3188 |
txt.textContent = alterStr(n.alter);
|
|
|
|
| 3478 |
accLabel.dataset.idx = idx;
|
| 3479 |
markerSvg.appendChild(accLabel);
|
| 3480 |
}
|
| 3481 |
+
accLabel.setAttribute("x", n.px + 8);
|
| 3482 |
+
accLabel.setAttribute("y", n.py + 5);
|
| 3483 |
accLabel.textContent = accStr;
|
| 3484 |
} else if (accLabel) {
|
| 3485 |
accLabel.remove();
|
|
|
|
| 3925 |
newPitch: stepOctaveToOmrPitch(n.step, n.octave, n.clef.sign) });
|
| 3926 |
}
|
| 3927 |
recomputeAndUpdate(selectedIdx);
|
| 3928 |
+
_previewNote(n);
|
| 3929 |
}
|
| 3930 |
|
| 3931 |
function lowerNote(skipUndo) {
|
|
|
|
| 3959 |
newPitch: stepOctaveToOmrPitch(n.step, n.octave, n.clef.sign) });
|
| 3960 |
}
|
| 3961 |
recomputeAndUpdate(selectedIdx);
|
| 3962 |
+
_previewNote(n);
|
| 3963 |
}
|
| 3964 |
|
| 3965 |
function setAccidental(alterValue, skipUndo) {
|
|
|
|
| 3995 |
}
|
| 3996 |
}
|
| 3997 |
recomputeAndUpdate(selectedIdx);
|
| 3998 |
+
_previewNote(n);
|
| 3999 |
}
|
| 4000 |
|
| 4001 |
// ── Note Deletion ────────────────────────────────────────────
|
|
|
|
| 6455 |
markerSvg.setAttribute("width", scoreImage.naturalWidth);
|
| 6456 |
markerSvg.setAttribute("height", scoreImage.naturalHeight);
|
| 6457 |
|
| 6458 |
+
// Auto-fit zoom to canvas width on first load
|
| 6459 |
+
if (!pg._zoomApplied) {
|
| 6460 |
+
const wrapper = document.getElementById("canvas-wrapper");
|
| 6461 |
+
const fitZoom = Math.floor((wrapper.clientWidth / scoreImage.naturalWidth) * 100);
|
| 6462 |
+
const clampedZoom = Math.max(25, Math.min(200, fitZoom));
|
| 6463 |
+
zoomSlider.value = clampedZoom;
|
| 6464 |
+
applyZoom(clampedZoom);
|
| 6465 |
+
pg._zoomApplied = true;
|
| 6466 |
+
}
|
| 6467 |
+
|
| 6468 |
renderMarkers(noteInfos);
|
| 6469 |
if (selectedIdx >= 0 && selectedIdx < noteInfos.length) selectNote(selectedIdx);
|
| 6470 |
else { selectedIdx = -1; statusSel.textContent = t("no_sel"); }
|
|
|
|
| 6514 |
markerSvg.setAttribute("width", scoreImage.naturalWidth);
|
| 6515 |
markerSvg.setAttribute("height", scoreImage.naturalHeight);
|
| 6516 |
|
| 6517 |
+
// Auto-fit zoom to canvas width on first load
|
| 6518 |
+
if (!pg._zoomApplied) {
|
| 6519 |
+
const wrapper = document.getElementById("canvas-wrapper");
|
| 6520 |
+
const fitZoom = Math.floor((wrapper.clientWidth / scoreImage.naturalWidth) * 100);
|
| 6521 |
+
const clampedZoom = Math.max(25, Math.min(200, fitZoom));
|
| 6522 |
+
zoomSlider.value = clampedZoom;
|
| 6523 |
+
applyZoom(clampedZoom);
|
| 6524 |
+
pg._zoomApplied = true;
|
| 6525 |
+
}
|
| 6526 |
+
|
| 6527 |
// Parse XML — from pre-loaded text or from file
|
| 6528 |
let xmlText;
|
| 6529 |
if (pg.xmlText) {
|
|
|
|
| 7237 |
let playbackStartOffset = 0; // seconds offset for seek
|
| 7238 |
let cursorSeekTime = 0; // time in seconds where the red cursor is parked
|
| 7239 |
|
| 7240 |
+
function _previewNote(n) {
|
| 7241 |
+
if (!n || n.isRest) return;
|
| 7242 |
+
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
| 7243 |
+
const freq = noteToFreq(n.step, n.octave, n.alter);
|
| 7244 |
+
playNoteSound(freq, 0.3, 0, n.step, n.octave, n.alter);
|
| 7245 |
+
}
|
| 7246 |
+
|
| 7247 |
function noteToFreq(step, octave, alter) {
|
| 7248 |
const midi = (octave + 1) * 12 + [0,2,4,5,7,9,11][STEP_INDEX[step]] + alter;
|
| 7249 |
return 440 * Math.pow(2, (midi - 69) / 12);
|
|
|
|
| 8474 |
recordOmrEdit({ type: "change_pitch", headId: n.omrHeadId,
|
| 8475 |
newPitch: stepOctaveToOmrPitch(n.step, n.octave, n.clef.sign) });
|
| 8476 |
}
|
| 8477 |
+
_previewNote(n);
|
| 8478 |
}
|
| 8479 |
if (dragIsXMode) {
|
| 8480 |
document.body.style.cursor = "";
|
|
|
|
| 8507 |
}
|
| 8508 |
const accLabel = markerSvg.querySelector(`text.acc-label[data-idx="${idx}"]`);
|
| 8509 |
if (accLabel) {
|
| 8510 |
+
accLabel.setAttribute("x", n.px + 8);
|
| 8511 |
}
|
| 8512 |
});
|
| 8513 |
}
|
|
|
|
| 8845 |
|
| 8846 |
document.addEventListener("mouseup", (e) => {
|
| 8847 |
if (staffDragIdx < 0 || !staffAdjustMode) return;
|
| 8848 |
+
|
| 8849 |
+
// Compute delta before clearing state — needed to update omrY/omrX
|
| 8850 |
+
const dy = (e.clientY - staffDragStartY) / currentZoom;
|
| 8851 |
+
const dx = (e.clientX - staffDragStartX) / currentZoom;
|
| 8852 |
+
const wasShift = staffDragShift;
|
| 8853 |
+
const draggedIdx = staffDragIdx;
|
| 8854 |
+
const numSPS = (systemsData.length > 0) ? systemsData[0].numStaves : 1;
|
| 8855 |
+
const dragSysIdx = Math.floor(draggedIdx / numSPS);
|
| 8856 |
+
const dragStaffInSys = (draggedIdx % numSPS) + 1;
|
| 8857 |
+
|
| 8858 |
+
// Update omrY/omrX for notes on the dragged staff so they don't snap back
|
| 8859 |
+
noteInfos.forEach(n => {
|
| 8860 |
+
if (n.systemIdx === dragSysIdx && n.staff === dragStaffInSys) {
|
| 8861 |
+
if (wasShift) {
|
| 8862 |
+
if (n.omrX != null) n.omrX += dx;
|
| 8863 |
+
} else {
|
| 8864 |
+
if (n.omrY != null) n.omrY += dy;
|
| 8865 |
+
}
|
| 8866 |
+
}
|
| 8867 |
+
});
|
| 8868 |
+
|
| 8869 |
staffDragIdx = -1;
|
| 8870 |
staffDragOrigData = null;
|
| 8871 |
staffDragDebugLines = [];
|
|
|
|
| 9762 |
const circle = markerSvg.querySelector(`circle.marker[data-idx="${idx}"]`);
|
| 9763 |
if (circle) circle.setAttribute("cx", n.px);
|
| 9764 |
const accLabel = markerSvg.querySelector(`text.acc-label[data-idx="${idx}"]`);
|
| 9765 |
+
if (accLabel) accLabel.setAttribute("x", n.px + 8);
|
| 9766 |
});
|
| 9767 |
return;
|
| 9768 |
}
|
|
@@ -1,75 +1,76 @@
|
|
| 1 |
/* ============================================================
|
| 2 |
-
Score → MML · Design Tokens (
|
| 3 |
------------------------------------------------------------
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
============================================================ */
|
| 13 |
|
| 14 |
-
/* Fonts ------------------------------------------------------ */
|
| 15 |
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
|
| 16 |
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap");
|
| 17 |
|
| 18 |
:root{
|
| 19 |
-
/* Surfaces
|
| 20 |
-
--bg-0: #0a0c10;
|
| 21 |
-
--bg-1: #0e1118;
|
| 22 |
-
--bg-2: #141822;
|
| 23 |
-
--bg-3: #1b2030;
|
| 24 |
-
--bg-4: #232a3c;
|
| 25 |
--line: #262c3a;
|
| 26 |
--line-strong: #323a4f;
|
| 27 |
|
| 28 |
-
/* Ink
|
| 29 |
-
--ink-0: #eef1f7;
|
| 30 |
-
--ink-1: #b9c0cf;
|
| 31 |
-
--ink-2: #7f8898;
|
| 32 |
-
--ink-3: #535b6c;
|
| 33 |
|
| 34 |
-
/*
|
| 35 |
-
--accent: #f3a25b;
|
| 36 |
--accent-dim: #a87246;
|
| 37 |
--accent-soft: rgba(243,162,91,.14);
|
| 38 |
|
| 39 |
-
/*
|
| 40 |
-
--
|
| 41 |
-
--
|
| 42 |
-
|
| 43 |
-
/* Grade / confidence — kept semantically distinct from accent */
|
| 44 |
-
--grade-low: #ff5252; /* matches sel-multi family */
|
| 45 |
-
--grade-mid: #ffae3a;
|
| 46 |
-
--grade-ok: #7cd454;
|
| 47 |
-
|
| 48 |
-
/* Voice palette — tuned for parity in saturation/luminance */
|
| 49 |
-
--v1: #6aa3ff; /* was #4488ff — softer */
|
| 50 |
-
--v2: #ff6c7a; /* was #ff4444 — distinct from grade-low */
|
| 51 |
-
--v3: #6dd17e; /* was #44aa44 — distinct from grade-ok */
|
| 52 |
-
--v4: #ffc24a; /* was #ffaa00 — distinct from grade-mid */
|
| 53 |
-
|
| 54 |
-
/* Barline edit overlays */
|
| 55 |
-
--bl-high: #7cd454;
|
| 56 |
-
--bl-medium: #ffae3a;
|
| 57 |
-
--bl-low: #d9882e;
|
| 58 |
-
--bl-manual: #80b7ff;
|
| 59 |
-
--bl-implicit: #cc66cc;
|
| 60 |
-
--bl-selected: #5a99ff;
|
| 61 |
|
| 62 |
/* Type */
|
| 63 |
--font-sans: "Pretendard Variable", Pretendard, ui-sans-serif, sans-serif;
|
| 64 |
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
| 65 |
|
| 66 |
-
/* Type scale —
|
| 67 |
-
--fs-display:
|
| 68 |
-
--fs-title:
|
| 69 |
-
--fs-body: 13px;
|
| 70 |
-
--fs-label: 12px;
|
| 71 |
-
--fs-meta: 11.5px;
|
| 72 |
-
--fs-kbd: 10.5px;
|
| 73 |
|
| 74 |
/* Spacing */
|
| 75 |
--space-1: 4px;
|
|
@@ -92,7 +93,7 @@
|
|
| 92 |
}
|
| 93 |
|
| 94 |
/* ============================================================
|
| 95 |
-
GLOBAL DEFAULTS
|
| 96 |
============================================================ */
|
| 97 |
|
| 98 |
html, body {
|
|
@@ -104,17 +105,15 @@ html, body {
|
|
| 104 |
text-rendering: geometricPrecision;
|
| 105 |
}
|
| 106 |
|
| 107 |
-
/* Tabular numerics on every mono span */
|
| 108 |
.mono, code, kbd, .kbd-chip {
|
| 109 |
font-family: var(--font-mono);
|
| 110 |
font-variant-numeric: tabular-nums;
|
| 111 |
}
|
| 112 |
|
| 113 |
/* ============================================================
|
| 114 |
-
COMPONENT PRIMITIVES
|
| 115 |
============================================================ */
|
| 116 |
|
| 117 |
-
/* Button */
|
| 118 |
.btn{
|
| 119 |
display:inline-flex; align-items:center; gap:6px;
|
| 120 |
height:30px; padding:0 12px; border-radius: var(--r-md);
|
|
@@ -137,13 +136,19 @@ html, body {
|
|
| 137 |
.btn.ghost { background: transparent; border-color: var(--line); }
|
| 138 |
.btn.ghost:hover { background: var(--bg-3); }
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
.btn.danger {
|
| 141 |
background: rgba(255,93,93,.12);
|
| 142 |
border-color: rgba(255,93,93,.35);
|
| 143 |
color: #ffb3b3;
|
| 144 |
}
|
| 145 |
|
| 146 |
-
/* Icon button */
|
| 147 |
.icon-btn{
|
| 148 |
width:30px; height:30px; display:inline-flex; align-items:center; justify-content:center;
|
| 149 |
background:transparent; border:1px solid transparent;
|
|
@@ -151,7 +156,6 @@ html, body {
|
|
| 151 |
}
|
| 152 |
.icon-btn:hover { background: var(--bg-3); color: var(--ink-0); }
|
| 153 |
|
| 154 |
-
/* Keyboard chip */
|
| 155 |
.kbd-chip{
|
| 156 |
display:inline-block;
|
| 157 |
font-family: var(--font-mono); font-size: var(--fs-kbd);
|
|
@@ -160,16 +164,15 @@ html, body {
|
|
| 160 |
line-height: 1.4;
|
| 161 |
}
|
| 162 |
|
| 163 |
-
/* Pill (status) */
|
| 164 |
.pill{
|
| 165 |
display:inline-flex; align-items:center; gap:6px;
|
| 166 |
padding: 5px 10px; border-radius: 999px;
|
| 167 |
background: var(--bg-2); border:1px solid var(--line);
|
| 168 |
color: var(--ink-1); font-size: var(--fs-label);
|
| 169 |
}
|
| 170 |
-
.pill .dot{ width:6px; height:6px; border-radius:50%; background: var(--
|
| 171 |
|
| 172 |
-
/*
|
| 173 |
.app-topbar{
|
| 174 |
height:48px; display:flex; align-items:center; gap:14px;
|
| 175 |
padding: 0 var(--space-4);
|
|
@@ -177,8 +180,6 @@ html, body {
|
|
| 177 |
border-bottom: 1px solid var(--line);
|
| 178 |
flex-shrink: 0;
|
| 179 |
}
|
| 180 |
-
|
| 181 |
-
/* Status bar */
|
| 182 |
.app-statusbar{
|
| 183 |
height:30px; display:flex; align-items:center; gap:18px;
|
| 184 |
padding: 0 var(--space-4);
|
|
@@ -189,12 +190,10 @@ html, body {
|
|
| 189 |
}
|
| 190 |
.app-statusbar .sep { width:1px; height:14px; background: var(--line); }
|
| 191 |
|
| 192 |
-
/* Tabs (Convert / Corrector) */
|
| 193 |
.tabs{ display:flex; gap:2px; background: var(--bg-2); padding:3px; border-radius: 8px; border:1px solid var(--line); }
|
| 194 |
.tabs .tab{ padding: 5px 12px; border-radius: 6px; font-size: 12.5px; color: var(--ink-2); cursor: pointer; }
|
| 195 |
.tabs .tab.is-active{ background: var(--bg-3); color: var(--ink-0); box-shadow: inset 0 0 0 1px var(--line-strong); }
|
| 196 |
|
| 197 |
-
/* Panel header */
|
| 198 |
.panel-head{
|
| 199 |
display:flex; align-items:center; gap:8px;
|
| 200 |
padding: 10px 14px;
|
|
@@ -206,7 +205,6 @@ html, body {
|
|
| 206 |
text-transform: uppercase; color: var(--ink-2); margin: 0;
|
| 207 |
}
|
| 208 |
|
| 209 |
-
/* Mode pill (shows current corrector mode) */
|
| 210 |
.mode-pill{
|
| 211 |
padding: 3px 10px; border-radius: 999px;
|
| 212 |
font-size: 11px; font-weight: 700; letter-spacing: .06em;
|
|
@@ -216,7 +214,6 @@ html, body {
|
|
| 216 |
.mode-pill.add{ background: rgba(106,163,255,.16); color: #cfe2ff; border-color: rgba(106,163,255,.4); }
|
| 217 |
.mode-pill.glyph{ background: rgba(255,153,51,.16); color: #ffcc88; border-color: rgba(255,153,51,.4); }
|
| 218 |
|
| 219 |
-
/* Slider */
|
| 220 |
.slider{
|
| 221 |
appearance: none; height: 4px; border-radius: 99px;
|
| 222 |
background: var(--bg-3); outline: none; width: 120px;
|
|
@@ -226,8 +223,17 @@ html, body {
|
|
| 226 |
background: var(--accent); border: 2px solid #1a1004; cursor: pointer;
|
| 227 |
}
|
| 228 |
|
| 229 |
-
/*
|
| 230 |
-
input[type="number"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
background: #0a0d14;
|
| 232 |
border: 1px solid var(--line);
|
| 233 |
border-radius: 5px;
|
|
@@ -236,70 +242,11 @@ input[type="number"], input[type="text"], select{
|
|
| 236 |
font-family: var(--font-mono);
|
| 237 |
font-size: 11.5px;
|
| 238 |
}
|
| 239 |
-
input:focus,
|
|
|
|
|
|
|
|
|
|
| 240 |
outline: none;
|
| 241 |
border-color: var(--accent-dim);
|
| 242 |
box-shadow: 0 0 0 2px var(--accent-soft);
|
| 243 |
}
|
| 244 |
-
|
| 245 |
-
/* ============================================================
|
| 246 |
-
MARKER OVERLAY (score notes) — REPLACES current .marker rules
|
| 247 |
-
============================================================ */
|
| 248 |
-
|
| 249 |
-
.marker{ pointer-events: all; cursor: pointer; transition: r .1s, stroke-width .1s; }
|
| 250 |
-
.marker:hover{ r: 10; }
|
| 251 |
-
|
| 252 |
-
/* Voice fill */
|
| 253 |
-
.marker.voice1{ fill: rgba(106,163,255,.78); stroke: #4a7adc; stroke-width: 1; }
|
| 254 |
-
.marker.voice2{ fill: rgba(255,108,122,.78); stroke: #d44352; stroke-width: 1; }
|
| 255 |
-
.marker.voice3{ fill: rgba(109,209,126,.78); stroke: #4ca85f; stroke-width: 1; }
|
| 256 |
-
.marker.voice4{ fill: rgba(255,194,74,.78); stroke: #d49832; stroke-width: 1; }
|
| 257 |
-
.marker.rest-marker{ fill: rgba(120,128,144,.4); stroke: #5a6273; stroke-width: 1; r: 6; }
|
| 258 |
-
|
| 259 |
-
/* Grade confidence */
|
| 260 |
-
.marker.grade-low{ fill: rgba(255,80,80,.85); stroke: var(--grade-low); stroke-width: 2;
|
| 261 |
-
filter: drop-shadow(0 0 4px rgba(255,82,82,.6)); }
|
| 262 |
-
.marker.grade-mid{ fill: rgba(255,174,58,.85); stroke: var(--grade-mid); stroke-width: 1.5; }
|
| 263 |
-
.marker.grade-ok{ fill: rgba(124,212,84,.7); stroke: var(--grade-ok); stroke-width: 1; }
|
| 264 |
-
|
| 265 |
-
/* Selection (preserve worklog #8 — multi must beat voice colors) */
|
| 266 |
-
.marker.selected{
|
| 267 |
-
stroke: var(--sel-single); stroke-width: 4; r: 12;
|
| 268 |
-
filter: drop-shadow(0 0 8px var(--sel-single)) drop-shadow(0 0 16px rgba(255,216,107,.5));
|
| 269 |
-
}
|
| 270 |
-
.marker.multi-selected{
|
| 271 |
-
stroke: var(--sel-multi) !important; stroke-width: 4 !important; r: 11;
|
| 272 |
-
filter: drop-shadow(0 0 4px rgba(255,93,93,.8)) !important;
|
| 273 |
-
}
|
| 274 |
-
.marker.multi-selected.selected{
|
| 275 |
-
stroke: var(--sel-multi) !important; stroke-width: 5 !important; r: 12;
|
| 276 |
-
filter: drop-shadow(0 0 6px rgba(255,93,93,.9)) !important;
|
| 277 |
-
}
|
| 278 |
-
|
| 279 |
-
.marker.modified{ stroke: var(--grade-ok); stroke-width: 2.5;
|
| 280 |
-
filter: drop-shadow(0 0 3px var(--grade-ok)); }
|
| 281 |
-
.marker.modified.selected{ stroke: var(--sel-single); stroke-width: 3;
|
| 282 |
-
filter: drop-shadow(0 0 4px var(--grade-ok)); }
|
| 283 |
-
|
| 284 |
-
.marker.playback-highlight{ stroke: var(--grade-low); stroke-width: 3; r: 11;
|
| 285 |
-
fill: rgba(255,80,80,.9); }
|
| 286 |
-
|
| 287 |
-
/* Rubber-band selection */
|
| 288 |
-
.score-rubber{
|
| 289 |
-
border: 2px dashed var(--sel-multi);
|
| 290 |
-
background: rgba(255,93,93,.08);
|
| 291 |
-
pointer-events: none; z-index: 100;
|
| 292 |
-
}
|
| 293 |
-
.tl-rubber{
|
| 294 |
-
border: 2px dashed var(--sel-single);
|
| 295 |
-
background: rgba(255,216,107,.10);
|
| 296 |
-
pointer-events: none; z-index: 100;
|
| 297 |
-
}
|
| 298 |
-
|
| 299 |
-
/* Barline overlays */
|
| 300 |
-
.barline-overlay.high { stroke: var(--bl-high); stroke-width: 1.5; opacity: .55; }
|
| 301 |
-
.barline-overlay.medium { stroke: var(--bl-medium); stroke-width: 1.5; opacity: .55; }
|
| 302 |
-
.barline-overlay.low { stroke: var(--bl-low); stroke-width: 1.5; opacity: .55; stroke-dasharray: 4 4; }
|
| 303 |
-
.barline-overlay.manual { stroke: var(--bl-manual); stroke-width: 2; opacity: .75; }
|
| 304 |
-
.barline-overlay.implicit { stroke: var(--bl-implicit); stroke-width: 2; opacity: .65; stroke-dasharray: 6 3; }
|
| 305 |
-
.barline-overlay.bl-selected { stroke: var(--bl-selected); stroke-width: 3; opacity: 1; }
|
|
|
|
| 1 |
/* ============================================================
|
| 2 |
+
Score → MML · Design Tokens · v3 (2026-05-17)
|
| 3 |
------------------------------------------------------------
|
| 4 |
+
STATUS: this file is ALREADY linked from corrector.html as the
|
| 5 |
+
FIRST stylesheet (before corrector.css).
|
| 6 |
+
|
| 7 |
+
PHILOSOPHY (v3):
|
| 8 |
+
"Calm chrome, working workspace."
|
| 9 |
+
|
| 10 |
+
- Chrome (topbar, toolbar, panels, status, popups' wrapper):
|
| 11 |
+
slate neutrals + ONE amber action accent. Restrained.
|
| 12 |
+
- Score canvas overlays (markers, pulse, accidental labels,
|
| 13 |
+
voice colours, grade colours, rubber-band): UNCHANGED.
|
| 14 |
+
These are user-tuned & functional, not decorative.
|
| 15 |
+
- Timeline: compact (24px lanes), DAW-style voice gradients
|
| 16 |
+
retained, but pink chrome (border-left, title glow) dropped.
|
| 17 |
+
- Popups' chrome (chord neutral, glyph orange) keeps its
|
| 18 |
+
semantic colour but in token form.
|
| 19 |
+
|
| 20 |
+
This file owns:
|
| 21 |
+
:root tokens
|
| 22 |
+
html/body defaults
|
| 23 |
+
.btn .icon-btn .kbd-chip .pill .tabs .panel-head .mode-pill
|
| 24 |
+
.app-topbar .app-statusbar .slider input
|
| 25 |
+
|
| 26 |
+
This file does NOT redefine any .marker, .barline-overlay,
|
| 27 |
+
.free-glyph-box, .score-rubber, .tl-rubber, .tl-note, .acc-label,
|
| 28 |
+
.ghost-marker, .ghost-label, .rhythm-warning, .ghost-barline,
|
| 29 |
+
.measure-highlight, .barline-label, .debug-line, .staff-overlay,
|
| 30 |
+
.playback-cursor, #chord-popup, #glyph-popup rule.
|
| 31 |
+
Those live in corrector.css and stay there. See PRESERVED_VALUES.md.
|
| 32 |
============================================================ */
|
| 33 |
|
|
|
|
| 34 |
@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css");
|
| 35 |
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap");
|
| 36 |
|
| 37 |
:root{
|
| 38 |
+
/* Surfaces */
|
| 39 |
+
--bg-0: #0a0c10; /* canvas surround */
|
| 40 |
+
--bg-1: #0e1118; /* app shell */
|
| 41 |
+
--bg-2: #141822; /* panels */
|
| 42 |
+
--bg-3: #1b2030; /* raised: buttons, inputs */
|
| 43 |
+
--bg-4: #232a3c; /* button hover */
|
| 44 |
--line: #262c3a;
|
| 45 |
--line-strong: #323a4f;
|
| 46 |
|
| 47 |
+
/* Ink */
|
| 48 |
+
--ink-0: #eef1f7;
|
| 49 |
+
--ink-1: #b9c0cf;
|
| 50 |
+
--ink-2: #7f8898;
|
| 51 |
+
--ink-3: #535b6c;
|
| 52 |
|
| 53 |
+
/* Accent (action only) */
|
| 54 |
+
--accent: #f3a25b;
|
| 55 |
--accent-dim: #a87246;
|
| 56 |
--accent-soft: rgba(243,162,91,.14);
|
| 57 |
|
| 58 |
+
/* Status semantic */
|
| 59 |
+
--ok: #7cd454;
|
| 60 |
+
--warn: #ffae3a;
|
| 61 |
+
--danger:#ff5d5d;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
/* Type */
|
| 64 |
--font-sans: "Pretendard Variable", Pretendard, ui-sans-serif, sans-serif;
|
| 65 |
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
| 66 |
|
| 67 |
+
/* Type scale (UI chrome only — does NOT apply to .acc-label or popup contents) */
|
| 68 |
+
--fs-display: 18px; /* topbar brand, page title */
|
| 69 |
+
--fs-title: 14px; /* section headers, panel heads */
|
| 70 |
+
--fs-body: 13px; /* default UI */
|
| 71 |
+
--fs-label: 12px; /* form labels, toolbar buttons */
|
| 72 |
+
--fs-meta: 11.5px; /* status bar, helper text */
|
| 73 |
+
--fs-kbd: 10.5px; /* keyboard hints */
|
| 74 |
|
| 75 |
/* Spacing */
|
| 76 |
--space-1: 4px;
|
|
|
|
| 93 |
}
|
| 94 |
|
| 95 |
/* ============================================================
|
| 96 |
+
GLOBAL DEFAULTS
|
| 97 |
============================================================ */
|
| 98 |
|
| 99 |
html, body {
|
|
|
|
| 105 |
text-rendering: geometricPrecision;
|
| 106 |
}
|
| 107 |
|
|
|
|
| 108 |
.mono, code, kbd, .kbd-chip {
|
| 109 |
font-family: var(--font-mono);
|
| 110 |
font-variant-numeric: tabular-nums;
|
| 111 |
}
|
| 112 |
|
| 113 |
/* ============================================================
|
| 114 |
+
COMPONENT PRIMITIVES
|
| 115 |
============================================================ */
|
| 116 |
|
|
|
|
| 117 |
.btn{
|
| 118 |
display:inline-flex; align-items:center; gap:6px;
|
| 119 |
height:30px; padding:0 12px; border-radius: var(--r-md);
|
|
|
|
| 136 |
.btn.ghost { background: transparent; border-color: var(--line); }
|
| 137 |
.btn.ghost:hover { background: var(--bg-3); }
|
| 138 |
|
| 139 |
+
.btn.success { /* for #btn-download (retokened green) */
|
| 140 |
+
background: rgba(124,212,84,.12);
|
| 141 |
+
border-color: rgba(124,212,84,.4);
|
| 142 |
+
color: #b6e4a4;
|
| 143 |
+
}
|
| 144 |
+
.btn.success:hover { background: rgba(124,212,84,.2); }
|
| 145 |
+
|
| 146 |
.btn.danger {
|
| 147 |
background: rgba(255,93,93,.12);
|
| 148 |
border-color: rgba(255,93,93,.35);
|
| 149 |
color: #ffb3b3;
|
| 150 |
}
|
| 151 |
|
|
|
|
| 152 |
.icon-btn{
|
| 153 |
width:30px; height:30px; display:inline-flex; align-items:center; justify-content:center;
|
| 154 |
background:transparent; border:1px solid transparent;
|
|
|
|
| 156 |
}
|
| 157 |
.icon-btn:hover { background: var(--bg-3); color: var(--ink-0); }
|
| 158 |
|
|
|
|
| 159 |
.kbd-chip{
|
| 160 |
display:inline-block;
|
| 161 |
font-family: var(--font-mono); font-size: var(--fs-kbd);
|
|
|
|
| 164 |
line-height: 1.4;
|
| 165 |
}
|
| 166 |
|
|
|
|
| 167 |
.pill{
|
| 168 |
display:inline-flex; align-items:center; gap:6px;
|
| 169 |
padding: 5px 10px; border-radius: 999px;
|
| 170 |
background: var(--bg-2); border:1px solid var(--line);
|
| 171 |
color: var(--ink-1); font-size: var(--fs-label);
|
| 172 |
}
|
| 173 |
+
.pill .dot{ width:6px; height:6px; border-radius:50%; background: var(--ok); }
|
| 174 |
|
| 175 |
+
/* App shell */
|
| 176 |
.app-topbar{
|
| 177 |
height:48px; display:flex; align-items:center; gap:14px;
|
| 178 |
padding: 0 var(--space-4);
|
|
|
|
| 180 |
border-bottom: 1px solid var(--line);
|
| 181 |
flex-shrink: 0;
|
| 182 |
}
|
|
|
|
|
|
|
| 183 |
.app-statusbar{
|
| 184 |
height:30px; display:flex; align-items:center; gap:18px;
|
| 185 |
padding: 0 var(--space-4);
|
|
|
|
| 190 |
}
|
| 191 |
.app-statusbar .sep { width:1px; height:14px; background: var(--line); }
|
| 192 |
|
|
|
|
| 193 |
.tabs{ display:flex; gap:2px; background: var(--bg-2); padding:3px; border-radius: 8px; border:1px solid var(--line); }
|
| 194 |
.tabs .tab{ padding: 5px 12px; border-radius: 6px; font-size: 12.5px; color: var(--ink-2); cursor: pointer; }
|
| 195 |
.tabs .tab.is-active{ background: var(--bg-3); color: var(--ink-0); box-shadow: inset 0 0 0 1px var(--line-strong); }
|
| 196 |
|
|
|
|
| 197 |
.panel-head{
|
| 198 |
display:flex; align-items:center; gap:8px;
|
| 199 |
padding: 10px 14px;
|
|
|
|
| 205 |
text-transform: uppercase; color: var(--ink-2); margin: 0;
|
| 206 |
}
|
| 207 |
|
|
|
|
| 208 |
.mode-pill{
|
| 209 |
padding: 3px 10px; border-radius: 999px;
|
| 210 |
font-size: 11px; font-weight: 700; letter-spacing: .06em;
|
|
|
|
| 214 |
.mode-pill.add{ background: rgba(106,163,255,.16); color: #cfe2ff; border-color: rgba(106,163,255,.4); }
|
| 215 |
.mode-pill.glyph{ background: rgba(255,153,51,.16); color: #ffcc88; border-color: rgba(255,153,51,.4); }
|
| 216 |
|
|
|
|
| 217 |
.slider{
|
| 218 |
appearance: none; height: 4px; border-radius: 99px;
|
| 219 |
background: var(--bg-3); outline: none; width: 120px;
|
|
|
|
| 223 |
background: var(--accent); border: 2px solid #1a1004; cursor: pointer;
|
| 224 |
}
|
| 225 |
|
| 226 |
+
/* Inputs — scope to chrome (not popups; corrector.js sets inline styles) */
|
| 227 |
+
.app-topbar input[type="number"],
|
| 228 |
+
.app-topbar input[type="text"],
|
| 229 |
+
.app-topbar select,
|
| 230 |
+
#mode-panel input[type="number"],
|
| 231 |
+
#mode-panel input[type="text"],
|
| 232 |
+
#mode-panel select,
|
| 233 |
+
#toolbar input[type="number"],
|
| 234 |
+
#toolbar input[type="range"],
|
| 235 |
+
#upload-bar input[type="number"],
|
| 236 |
+
#upload-bar input[type="file"]{
|
| 237 |
background: #0a0d14;
|
| 238 |
border: 1px solid var(--line);
|
| 239 |
border-radius: 5px;
|
|
|
|
| 242 |
font-family: var(--font-mono);
|
| 243 |
font-size: 11.5px;
|
| 244 |
}
|
| 245 |
+
.app-topbar input:focus,
|
| 246 |
+
#mode-panel input:focus,
|
| 247 |
+
#toolbar input:focus,
|
| 248 |
+
#upload-bar input:focus{
|
| 249 |
outline: none;
|
| 250 |
border-color: var(--accent-dim);
|
| 251 |
box-shadow: 0 0 0 2px var(--accent-soft);
|
| 252 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|