@package-uploader/ui 1.1.3 → 1.1.4

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.
@@ -1 +1 @@
1
- :root{--color-primary: #2563eb;--color-primary-hover: #1d4ed8;--color-success: #10b981;--color-error: #ef4444;--color-warning: #f59e0b;--color-bg: #ffffff;--color-bg-secondary: #f9fafb;--color-border: #e5e7eb;--color-text: #111827;--color-text-muted: #6b7280;--radius: 8px;--radius-sm: 4px;--shadow: 0 1px 3px rgba(0, 0, 0, .1);--shadow-lg: 0 4px 6px rgba(0, 0, 0, .1)}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background:var(--color-bg-secondary);color:var(--color-text);line-height:1.5}.app{min-height:100vh;display:flex;flex-direction:column}.header{background:var(--color-bg);border-bottom:1px solid var(--color-border);padding:1rem 2rem;display:flex;align-items:center;justify-content:space-between}.header h1{font-size:1.25rem;font-weight:600}.nav{display:flex;gap:1rem}.nav a{color:var(--color-text-muted);text-decoration:none;padding:.5rem 1rem;border-radius:var(--radius);transition:all .2s}.nav a:hover,.nav a.active{color:var(--color-primary);background:#2563eb1a}.main{flex:1;padding:2rem;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1.5rem;box-shadow:var(--shadow)}.card+.card{margin-top:1rem}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.card-title{font-size:1.125rem;font-weight:600}.btn{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:var(--radius);border:none;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-primary{background:var(--color-primary);color:#fff}.btn-primary:hover{background:var(--color-primary-hover)}.btn-secondary{background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-text)}.btn-secondary:hover{background:var(--color-bg-secondary)}.btn:disabled{opacity:.5;cursor:not-allowed}.dropzone{border:2px dashed var(--color-border);border-radius:var(--radius);padding:3rem;text-align:center;cursor:pointer;transition:all .2s}.dropzone:hover,.dropzone.active{border-color:var(--color-primary);background:#2563eb0d}.dropzone p{color:var(--color-text-muted);margin-top:.5rem}.file-list{margin-top:1rem}.file-item{display:flex;align-items:center;justify-content:space-between;padding:.75rem;border:1px solid var(--color-border);border-radius:var(--radius);margin-bottom:.5rem}.file-item .name{font-weight:500}.file-item .size{color:var(--color-text-muted);font-size:.875rem}.file-item .status{display:flex;align-items:center;gap:.5rem}.status-pending{color:var(--color-text-muted)}.status-uploading{color:var(--color-primary)}.status-success{color:var(--color-success)}.status-error{color:var(--color-error)}.document-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.document-card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;transition:all .2s}.document-card:hover{box-shadow:var(--shadow-lg)}.document-card .title{font-weight:600;margin-bottom:.25rem}.document-card .meta{color:var(--color-text-muted);font-size:.875rem;margin-bottom:.75rem}.document-card .actions{display:flex;gap:.5rem}.folder-tree{border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;max-height:400px;overflow-y:auto}.folder-item{padding:.5rem;cursor:pointer;border-radius:var(--radius);display:flex;align-items:center;gap:.5rem}.folder-item:hover{background:var(--color-bg-secondary)}.folder-item.selected{background:#2563eb1a;color:var(--color-primary)}.folder-item .icon{font-size:1.25rem}.modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.modal{background:var(--color-bg);border-radius:var(--radius);padding:1.5rem;max-width:500px;width:90%;max-height:80vh;overflow-y:auto}.modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.modal-title{font-size:1.125rem;font-weight:600}.modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--color-text-muted)}.form-group{margin-bottom:1rem}.form-label{display:block;font-weight:500;margin-bottom:.25rem}.form-input,.form-select{width:100%;padding:.5rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:1rem}.form-input:focus,.form-select:focus{outline:none;border-color:var(--color-primary)}.spinner{width:20px;height:20px;border:2px solid var(--color-border);border-top-color:var(--color-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:3rem;color:var(--color-text-muted)}.empty-state h3{color:var(--color-text);margin-bottom:.5rem}.alert{padding:1rem;border-radius:var(--radius);margin-bottom:1rem}.alert-success{background:#10b9811a;border:1px solid var(--color-success);color:var(--color-success)}.alert-error{background:#ef44441a;border:1px solid var(--color-error);color:var(--color-error)}.alert-warning{background:#f59e0b1a;border:1px solid var(--color-warning);color:var(--color-warning)}.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:0;font-size:.875rem}.breadcrumb-item{background:none;border:none;color:var(--color-primary);cursor:pointer;padding:.25rem .5rem;border-radius:var(--radius);font-size:inherit}.breadcrumb-item:hover{background:#2563eb1a}.breadcrumb-current{color:var(--color-text);cursor:default}.breadcrumb-current:hover{background:transparent}.breadcrumb-separator{color:var(--color-text-muted);margin:0 .25rem}.browse-page{display:flex;flex-direction:column;gap:1rem}.toolbar{display:flex;justify-content:space-between;align-items:center;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:.75rem 1rem;gap:1rem;flex-wrap:wrap}.toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1;min-width:200px}.toolbar-right{display:flex;align-items:center;gap:.5rem}.search-input{padding:.5rem .75rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:.875rem;width:200px}.search-input:focus{outline:none;border-color:var(--color-primary)}.content-area{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.data-grid-container{display:flex;flex-direction:column}.data-grid{width:100%;border-collapse:collapse;font-size:.875rem}.data-grid thead{background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.data-grid th{text-align:left;padding:.75rem 1rem;font-weight:600;color:var(--color-text-muted);white-space:nowrap}.data-grid th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.data-grid th.sortable:hover{color:var(--color-text)}.data-grid td{padding:.75rem 1rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.grid-row{transition:background .15s}.grid-row:hover{background:var(--color-bg-secondary)}.folder-row{cursor:pointer}.folder-row:hover{background:#2563eb0d}.col-checkbox{width:40px;text-align:center}.col-name{min-width:250px}.col-format,.col-type{width:120px}.col-updated,.col-created{width:140px}.col-actions{width:100px;text-align:right}.item-icon{margin-right:.5rem;font-size:1rem}.item-name{font-weight:500}.subfolder-count{color:var(--color-text-muted);font-size:.75rem;margin-left:.5rem}.btn-icon{background:none;border:none;padding:.25rem .5rem;cursor:pointer;font-size:1rem;opacity:.6;transition:opacity .15s}.btn-icon:hover{opacity:1}.btn-icon:disabled{opacity:.3;cursor:not-allowed}.btn-icon.pinned,.btn-delete:hover{opacity:1}.data-grid-footer{padding:.75rem 1rem;background:var(--color-bg-secondary);border-top:1px solid var(--color-border);font-size:.75rem;color:var(--color-text-muted)}.data-grid-loading,.data-grid-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem;text-align:center;color:var(--color-text-muted);gap:.5rem}.data-grid-empty .empty-icon{font-size:3rem;margin-bottom:.5rem}.data-grid-empty h3{color:var(--color-text);margin:0}.data-grid-empty p{margin:.5rem 0 1rem}.folder-browser{border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.folder-browser-toolbar{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.folder-browser-actions{display:flex;gap:.5rem}.folder-browser-list{max-height:300px;overflow-y:auto}.folder-browser-table{width:100%;border-collapse:collapse}.folder-browser-row{transition:background .15s}.folder-browser-row:hover{background:var(--color-bg-secondary)}.folder-browser-row.selected{background:#2563eb1a}.folder-browser-row td{padding:.5rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.folder-browser-icon{width:40px;text-align:center;font-size:1.25rem}.folder-browser-name{cursor:pointer;font-weight:500}.folder-browser-name:hover{color:var(--color-primary)}.subfolder-indicator{color:var(--color-text-muted);font-size:.75rem;font-weight:400;margin-left:.5rem}.folder-browser-select{width:100px;text-align:center}.folder-browser-delete{width:50px;text-align:center}.folder-browser-loading,.folder-browser-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;color:var(--color-text-muted);gap:.75rem}.upload-page{display:flex;flex-direction:column;gap:1rem}.selected-folder-badge{background:#2563eb1a;color:var(--color-primary);padding:.25rem .75rem;border-radius:999px;font-size:.75rem;font-weight:500}.upload-actions{margin-top:1rem;display:flex;gap:.5rem;justify-content:flex-end}.btn-sm{padding:.25rem .5rem;font-size:.75rem}.document-row{cursor:pointer;transition:background .15s}.document-row:hover{background:var(--color-bg-secondary)}.document-row.expanded{background:#2563eb0d}.expand-icon{display:inline-block;width:1rem;margin-right:.25rem;transition:transform .2s;color:var(--color-text-muted)}.expand-icon.expanded{transform:rotate(90deg)}.expanded-row td{padding:0!important;background:var(--color-bg-secondary);border-bottom:2px solid var(--color-border)}.course-detail-panel{padding:1rem;display:flex;flex-direction:column;gap:1rem}.course-detail-loading,.course-detail-error{display:flex;align-items:center;justify-content:center;gap:.75rem;padding:2rem;color:var(--color-text-muted)}.course-detail-error{color:var(--color-error);flex-direction:column}.course-detail-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem}.tile-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--color-border)}.tile-header h3{font-size:1rem;font-weight:600;margin:0}.btn-close{background:none;border:none;font-size:1.25rem;color:var(--color-text-muted);cursor:pointer;padding:.25rem;line-height:1;border-radius:var(--radius);transition:all .15s}.btn-close:hover{background:var(--color-bg-secondary);color:var(--color-text)}.detail-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem 1.5rem}.detail-item{display:flex;flex-direction:column;gap:.125rem}.detail-item.span-2{grid-column:span 2}.detail-label{font-size:.7rem;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.025em}.detail-value{font-size:.875rem;color:var(--color-text)}.detail-value.mono{font-family:SF Mono,Monaco,Consolas,monospace;font-size:.75rem;background:var(--color-bg-secondary);padding:.125rem .375rem;border-radius:4px}.detail-value.description{line-height:1.4;max-height:4.2em;overflow:hidden;text-overflow:ellipsis}.thin-pack-row{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem}.thin-pack-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;border-left:4px solid}.thin-pack-tile.public{border-left-color:var(--color-success)}.thin-pack-tile.private{border-left-color:var(--color-primary)}.thin-pack-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem}.thin-pack-icon{font-size:1.5rem}.thin-pack-titles{flex:1}.thin-pack-title{font-size:.9rem;font-weight:600;margin:0}.thin-pack-subtitle{font-size:.75rem;color:var(--color-text-muted);margin:.125rem 0 0}.thin-pack-content{display:flex;flex-direction:column;gap:.5rem}.thin-pack-error{font-size:.75rem;color:var(--color-error);padding:.5rem;background:#ef44441a;border-radius:4px}.thin-pack-status{display:flex;align-items:center;gap:.5rem;font-size:.8rem}.thin-pack-status.exists{color:var(--color-success)}.thin-pack-status.exists .status-icon{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;background:var(--color-success);color:#fff;border-radius:50%;font-size:.7rem}.thin-pack-status.missing{color:var(--color-text-muted);font-style:italic}.thin-pack-meta{display:flex;gap:1rem;font-size:.7rem;color:var(--color-text-muted)}.thin-pack-actions{display:flex;gap:.5rem;margin-top:.25rem}.thin-pack-slug-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-slug-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-slug-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-slug-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-slug-input input::placeholder{color:var(--color-text-muted);opacity:.6}.thin-pack-name-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-name-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-name-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.token-preview{font-size:.65rem;color:var(--color-text-muted);font-family:monospace;word-break:break-all}.thin-pack-visibility{display:flex;flex-direction:column;gap:.25rem;padding:.5rem;background:var(--color-bg-tertiary);border-radius:4px}.visibility-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.visibility-toggle input[type=checkbox]{width:16px;height:16px;cursor:pointer}.toggle-label{font-size:.85rem;font-weight:500}.visibility-hint{font-size:.7rem;color:var(--color-text-muted);margin-left:24px}.thin-pack-tile.lms{border-left-color:var(--color-success)}.thin-pack-tile.shared{border-left-color:var(--color-primary)}.upload-modal{width:600px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column}.upload-modal-content{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.upload-modal .dropzone{min-height:120px}.upload-file-list{display:flex;flex-direction:column;gap:.5rem}.upload-file-list h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-file-item{display:flex;justify-content:space-between;align-items:center;padding:.5rem;background:var(--color-bg-secondary);border-radius:8px}.upload-file-info{display:flex;flex-direction:column;gap:.125rem}.upload-file-name{font-weight:500;font-size:.875rem}.upload-file-size{font-size:.75rem;color:var(--color-text-muted)}.upload-file-status{display:flex;align-items:center;gap:.5rem}.upload-progress-container{display:flex;align-items:center;gap:.75rem;min-width:150px}.upload-progress-bar{flex:1;height:8px;background:var(--color-border);border-radius:4px;overflow:hidden}.upload-progress-fill{height:100%;background:var(--color-primary);transition:width .15s ease-out}.upload-progress-text{font-size:.75rem;font-weight:500;color:var(--color-text-muted);min-width:36px;text-align:right}.status-processing{color:var(--color-primary);font-size:.875rem}.upload-link-options{background:var(--color-bg-secondary);border-radius:var(--radius);padding:1rem;display:flex;flex-direction:column;gap:.75rem}.upload-link-options h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-link-option{display:flex;flex-direction:column;gap:.5rem}.upload-link-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.upload-link-toggle input[type=checkbox]{width:1rem;height:1rem;cursor:pointer}.upload-link-toggle span{font-size:.875rem}.upload-link-name-input{display:flex;flex-direction:column;gap:.25rem;padding-left:1.5rem}.upload-link-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.upload-link-name-input input:focus{outline:none;border-color:var(--color-primary)}.upload-link-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.slug-preview{font-size:.7rem;color:var(--color-text-muted);font-family:monospace}.modal-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:1rem;border-top:1px solid var(--color-border)}@media (max-width: 768px){.detail-grid{grid-template-columns:1fr}.detail-item.span-2{grid-column:span 1}.thin-pack-row{grid-template-columns:1fr}}.user-menu{display:flex;align-items:center;gap:.75rem;margin-left:auto}.user-info{display:flex;align-items:center;gap:.5rem}.user-avatar{width:2rem;height:2rem;border-radius:50%;background:var(--color-primary);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.user-name{font-size:.875rem;color:var(--color-text);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header{display:flex;align-items:center;gap:1.5rem}.header .nav{margin-right:auto}.course-structure-step{display:flex;flex-direction:column;gap:.5rem}.course-structure-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.25rem}.course-structure-header h4{margin:0;font-size:1rem;font-weight:600}.course-structure-meta{display:flex;align-items:center;gap:.5rem;font-size:.75rem;color:var(--color-text-muted)}.course-structure-badge{background:#2563eb1a;color:var(--color-primary);padding:.125rem .5rem;border-radius:999px;font-size:.7rem;font-weight:500}.tree-node{padding:.25rem 0}.tree-node-row{display:flex;align-items:center;gap:.375rem;min-height:1.5rem}.tree-node-icon{font-size:1rem;flex-shrink:0}.tree-node-label{font-weight:500;font-size:.875rem;flex:1}.tree-class-input{width:100%;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);margin-top:.25rem}.tree-class-input:focus{outline:none;border-color:var(--color-primary)}.tree-class-input::placeholder{color:var(--color-text-muted);opacity:.6}.tree-class-input-sm{max-width:200px;margin-top:0;margin-left:auto;flex-shrink:0}.tree-course{background:var(--color-bg-secondary);border:1px solid var(--color-border);border-radius:var(--radius);padding:.5rem .75rem}.tree-lessons{display:flex;flex-direction:column;gap:.125rem;max-height:400px;overflow-y:auto;border:1px solid var(--color-border);border-radius:var(--radius);padding:.25rem}.tree-lesson{border-bottom:1px solid var(--color-border)}.tree-lesson:last-child{border-bottom:none}.tree-node-lesson{cursor:pointer;padding:.375rem .5rem;border-radius:var(--radius-sm);transition:background .15s}.tree-node-lesson:hover{background:var(--color-bg-secondary)}.tree-expand{font-size:.625rem;width:1rem;text-align:center;color:var(--color-text-muted);flex-shrink:0}.tree-lesson-num{color:var(--color-text-muted);font-weight:600;font-size:.75rem}.tree-node-indicator{font-size:.65rem;color:var(--color-primary);background:#2563eb1a;padding:.0625rem .375rem;border-radius:999px;white-space:nowrap;flex-shrink:0}.tree-lesson-content{padding:.25rem .5rem .5rem 1.5rem}.tree-lesson-class{margin-bottom:.375rem}.tree-node-block{display:flex;align-items:center;gap:.375rem;padding:.1875rem 0}.tree-node-block .tree-node-row{flex:1;min-width:0}.tree-block-pipe{font-family:monospace;color:var(--color-border);font-size:.75rem;flex-shrink:0}.tree-block-type{font-size:.75rem;font-weight:500;color:var(--color-text);white-space:nowrap}.tree-block-title{font-size:.7rem;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}.structure-parsing-indicator{display:flex;align-items:center;gap:.75rem;padding:.75rem;color:var(--color-primary);font-size:.875rem}.structure-detected-badge{background:#10b9811a;color:var(--color-success);border:1px solid var(--color-success);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem}.structure-multi-note{font-size:.75rem;color:var(--color-text-muted);font-style:italic;padding:.25rem 0}.structure-summary-badge{background:#2563eb1a;color:var(--color-primary);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem;margin-top:.25rem}.structure-section{display:flex;flex-direction:column}.structure-toggle-btn{display:flex;align-items:center;gap:.5rem;width:100%;padding:.625rem .75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);cursor:pointer;font-size:.875rem;font-weight:500;color:var(--color-text);transition:all .2s}.structure-toggle-btn:hover:not(:disabled){border-color:var(--color-primary);background:#2563eb08}.structure-toggle-btn.open{border-color:var(--color-primary);border-bottom-left-radius:0;border-bottom-right-radius:0;background:#2563eb0d}.structure-toggle-btn.disabled{opacity:.5;cursor:not-allowed;color:var(--color-text-muted)}.structure-toggle-btn .spinner{width:16px;height:16px;flex-shrink:0}.structure-toggle-arrow{margin-left:auto;font-size:.625rem;color:var(--color-text-muted);transition:transform .2s}.structure-panel{border:1px solid var(--color-primary);border-top:none;border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);padding:.75rem;background:var(--color-bg);animation:panelSlideDown .15s ease-out}@keyframes panelSlideDown{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.block-cards{display:flex;flex-direction:column;gap:3px;padding:.375rem 0}.block-card{display:flex;flex-direction:column;gap:.25rem;padding:.5rem .625rem;border-radius:var(--radius-sm);border:1px solid var(--color-border);border-left:4px solid var(--block-accent, #6b7280);background:var(--color-bg);cursor:pointer;transition:all .15s}.block-card:hover:not(.grouped){border-color:var(--color-primary);border-left-color:var(--block-accent, #6b7280);background:#2563eb08}.block-card.selected{background:#2563eb14;border-color:var(--color-primary);border-left-color:var(--block-accent, #6b7280);box-shadow:0 0 0 1px #2563eb33}.block-card.grouped{cursor:default;opacity:.75;background:var(--color-bg-secondary)}.block-card-header{display:flex;align-items:center;gap:.5rem;min-height:1.25rem}.block-card-type{font-size:.8rem;font-weight:600;color:var(--color-text);white-space:nowrap}.block-card-title{font-size:.75rem;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.block-card-check{color:var(--color-primary);font-weight:700;font-size:.875rem;flex-shrink:0;margin-left:auto}.block-card .tree-class-input-sm{margin-left:0;max-width:100%;margin-top:0}.block-group-container{border:2px dashed var(--color-primary);border-radius:var(--radius);padding:.375rem;margin:.25rem 0;background:#2563eb05}.block-group-container .block-card{border-left-width:3px}.bgp-group-header{display:flex;align-items:center;gap:.5rem;padding:.25rem .5rem;font-size:.7rem;border-bottom:1px solid rgba(37,99,235,.15);margin-bottom:.25rem}.bgp-group-label{font-weight:600;color:var(--color-primary)}.bgp-group-class{color:var(--color-text-muted);font-family:monospace;font-size:.65rem}.bgp-btn{background:none;border:none;font-size:.65rem;cursor:pointer;padding:.125rem .375rem;border-radius:var(--radius-sm);transition:all .15s}.bgp-btn-edit{color:var(--color-text-muted);margin-left:auto}.bgp-btn-edit:hover{color:var(--color-primary);background:#2563eb1a}.bgp-btn-ungroup{color:var(--color-text-muted)}.bgp-btn-ungroup:hover{color:var(--color-error);background:#ef44441a}.bgp-btn-save{color:var(--color-success)}.bgp-btn-save:hover{background:#10b9811a}.bgp-btn-cancel{color:var(--color-text-muted)}.bgp-btn-cancel:hover{background:var(--color-bg-secondary)}.bgp-group-edit{display:flex;align-items:center;gap:.375rem;flex:1}.bgp-edit-input{padding:.1875rem .375rem;font-size:.7rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);flex:1}.bgp-edit-input:focus{outline:none;border-color:var(--color-primary)}.bgp-edit-input-sm{max-width:120px}.bgp-create{padding:.5rem .75rem;margin-top:.375rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-secondary);display:flex;flex-direction:column;gap:.375rem}.bgp-create-info{font-size:.75rem;font-weight:500;color:var(--color-text)}.bgp-validation-error{color:var(--color-error);font-weight:400}.bgp-create-inputs{display:flex;gap:.375rem}.bgp-create-class{flex:2;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-class:focus{outline:none;border-color:var(--color-primary)}.bgp-create-label{flex:1;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-label:focus{outline:none;border-color:var(--color-primary)}.bgp-create-actions{display:flex;gap:.375rem}.bgp-hint{padding:.75rem;font-size:.75rem;color:var(--color-text-muted);text-align:center;font-style:italic}
1
+ :root{--color-primary: #2563eb;--color-primary-hover: #1d4ed8;--color-success: #10b981;--color-error: #ef4444;--color-warning: #f59e0b;--color-bg: #ffffff;--color-bg-secondary: #f9fafb;--color-border: #e5e7eb;--color-text: #111827;--color-text-muted: #6b7280;--radius: 8px;--radius-sm: 4px;--shadow: 0 1px 3px rgba(0, 0, 0, .1);--shadow-lg: 0 4px 6px rgba(0, 0, 0, .1)}*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background:var(--color-bg-secondary);color:var(--color-text);line-height:1.5}.app{min-height:100vh;display:flex;flex-direction:column}.header{background:var(--color-bg);border-bottom:1px solid var(--color-border);padding:1rem 2rem;display:flex;align-items:center;justify-content:space-between}.header h1{font-size:1.25rem;font-weight:600}.nav{display:flex;gap:1rem}.nav a{color:var(--color-text-muted);text-decoration:none;padding:.5rem 1rem;border-radius:var(--radius);transition:all .2s}.nav a:hover,.nav a.active{color:var(--color-primary);background:#2563eb1a}.main{flex:1;padding:2rem;max-width:1200px;margin:0 auto;width:100%}.card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1.5rem;box-shadow:var(--shadow)}.card+.card{margin-top:1rem}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.card-title{font-size:1.125rem;font-weight:600}.btn{display:inline-flex;align-items:center;gap:.5rem;padding:.5rem 1rem;border-radius:var(--radius);border:none;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-primary{background:var(--color-primary);color:#fff}.btn-primary:hover{background:var(--color-primary-hover)}.btn-secondary{background:var(--color-bg);border:1px solid var(--color-border);color:var(--color-text)}.btn-secondary:hover{background:var(--color-bg-secondary)}.btn:disabled{opacity:.5;cursor:not-allowed}.dropzone{border:2px dashed var(--color-border);border-radius:var(--radius);padding:3rem;text-align:center;cursor:pointer;transition:all .2s}.dropzone:hover,.dropzone.active{border-color:var(--color-primary);background:#2563eb0d}.dropzone p{color:var(--color-text-muted);margin-top:.5rem}.file-list{margin-top:1rem}.file-item{display:flex;align-items:center;justify-content:space-between;padding:.75rem;border:1px solid var(--color-border);border-radius:var(--radius);margin-bottom:.5rem}.file-item .name{font-weight:500}.file-item .size{color:var(--color-text-muted);font-size:.875rem}.file-item .status{display:flex;align-items:center;gap:.5rem}.status-pending{color:var(--color-text-muted)}.status-uploading{color:var(--color-primary)}.status-success{color:var(--color-success)}.status-error{color:var(--color-error)}.document-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1rem}.document-card{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;transition:all .2s}.document-card:hover{box-shadow:var(--shadow-lg)}.document-card .title{font-weight:600;margin-bottom:.25rem}.document-card .meta{color:var(--color-text-muted);font-size:.875rem;margin-bottom:.75rem}.document-card .actions{display:flex;gap:.5rem}.folder-tree{border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;max-height:400px;overflow-y:auto}.folder-item{padding:.5rem;cursor:pointer;border-radius:var(--radius);display:flex;align-items:center;gap:.5rem}.folder-item:hover{background:var(--color-bg-secondary)}.folder-item.selected{background:#2563eb1a;color:var(--color-primary)}.folder-item .icon{font-size:1.25rem}.modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.modal{background:var(--color-bg);border-radius:var(--radius);padding:1.5rem;max-width:500px;width:90%;max-height:80vh;overflow-y:auto}.modal-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem}.modal-title{font-size:1.125rem;font-weight:600}.modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer;color:var(--color-text-muted)}.form-group{margin-bottom:1rem}.form-label{display:block;font-weight:500;margin-bottom:.25rem}.form-input,.form-select{width:100%;padding:.5rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:1rem}.form-input:focus,.form-select:focus{outline:none;border-color:var(--color-primary)}.spinner{width:20px;height:20px;border:2px solid var(--color-border);border-top-color:var(--color-primary);border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:3rem;color:var(--color-text-muted)}.empty-state h3{color:var(--color-text);margin-bottom:.5rem}.alert{padding:1rem;border-radius:var(--radius);margin-bottom:1rem}.alert-success{background:#10b9811a;border:1px solid var(--color-success);color:var(--color-success)}.alert-error{background:#ef44441a;border:1px solid var(--color-error);color:var(--color-error)}.alert-warning{background:#f59e0b1a;border:1px solid var(--color-warning);color:var(--color-warning)}.breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:0;font-size:.875rem}.breadcrumb-item{background:none;border:none;color:var(--color-primary);cursor:pointer;padding:.25rem .5rem;border-radius:var(--radius);font-size:inherit}.breadcrumb-item:hover{background:#2563eb1a}.breadcrumb-current{color:var(--color-text);cursor:default}.breadcrumb-current:hover{background:transparent}.breadcrumb-separator{color:var(--color-text-muted);margin:0 .25rem}.browse-page{display:flex;flex-direction:column;gap:1rem}.toolbar{display:flex;justify-content:space-between;align-items:center;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:.75rem 1rem;gap:1rem;flex-wrap:wrap}.toolbar-left{display:flex;align-items:center;gap:.5rem;flex:1;min-width:200px}.toolbar-right{display:flex;align-items:center;gap:.5rem}.search-input{padding:.5rem .75rem;border:1px solid var(--color-border);border-radius:var(--radius);font-size:.875rem;width:200px}.search-input:focus{outline:none;border-color:var(--color-primary)}.content-area{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.data-grid-container{display:flex;flex-direction:column}.data-grid{width:100%;border-collapse:collapse;font-size:.875rem}.data-grid thead{background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.data-grid th{text-align:left;padding:.75rem 1rem;font-weight:600;color:var(--color-text-muted);white-space:nowrap}.data-grid th.sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.data-grid th.sortable:hover{color:var(--color-text)}.data-grid td{padding:.75rem 1rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.grid-row{transition:background .15s}.grid-row:hover{background:var(--color-bg-secondary)}.folder-row{cursor:pointer}.folder-row:hover{background:#2563eb0d}.col-checkbox{width:40px;text-align:center}.col-name{min-width:250px}.col-format,.col-type{width:120px}.col-updated,.col-created{width:140px}.col-actions{width:100px;text-align:right}.item-icon{margin-right:.5rem;font-size:1rem}.item-name{font-weight:500}.subfolder-count{color:var(--color-text-muted);font-size:.75rem;margin-left:.5rem}.btn-icon{background:none;border:none;padding:.25rem .5rem;cursor:pointer;font-size:1rem;opacity:.6;transition:opacity .15s}.btn-icon:hover{opacity:1}.btn-icon:disabled{opacity:.3;cursor:not-allowed}.btn-icon.pinned,.btn-delete:hover{opacity:1}.data-grid-footer{padding:.75rem 1rem;background:var(--color-bg-secondary);border-top:1px solid var(--color-border);font-size:.75rem;color:var(--color-text-muted)}.data-grid-loading,.data-grid-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem;text-align:center;color:var(--color-text-muted);gap:.5rem}.data-grid-empty .empty-icon{font-size:3rem;margin-bottom:.5rem}.data-grid-empty h3{color:var(--color-text);margin:0}.data-grid-empty p{margin:.5rem 0 1rem}.folder-browser{border:1px solid var(--color-border);border-radius:var(--radius);overflow:hidden}.folder-browser-toolbar{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background:var(--color-bg-secondary);border-bottom:1px solid var(--color-border)}.folder-browser-actions{display:flex;gap:.5rem}.folder-browser-list{max-height:300px;overflow-y:auto}.folder-browser-table{width:100%;border-collapse:collapse}.folder-browser-row{transition:background .15s}.folder-browser-row:hover{background:var(--color-bg-secondary)}.folder-browser-row.selected{background:#2563eb1a}.folder-browser-row td{padding:.5rem;border-bottom:1px solid var(--color-border);vertical-align:middle}.folder-browser-icon{width:40px;text-align:center;font-size:1.25rem}.folder-browser-name{cursor:pointer;font-weight:500}.folder-browser-name:hover{color:var(--color-primary)}.subfolder-indicator{color:var(--color-text-muted);font-size:.75rem;font-weight:400;margin-left:.5rem}.folder-browser-select{width:100px;text-align:center}.folder-browser-delete{width:50px;text-align:center}.folder-browser-loading,.folder-browser-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;color:var(--color-text-muted);gap:.75rem}.upload-page{display:flex;flex-direction:column;gap:1rem}.selected-folder-badge{background:#2563eb1a;color:var(--color-primary);padding:.25rem .75rem;border-radius:999px;font-size:.75rem;font-weight:500}.upload-actions{margin-top:1rem;display:flex;gap:.5rem;justify-content:flex-end}.btn-sm{padding:.25rem .5rem;font-size:.75rem}.document-row{cursor:pointer;transition:background .15s}.document-row:hover{background:var(--color-bg-secondary)}.document-row.expanded{background:#2563eb0d}.expand-icon{display:inline-block;width:1rem;margin-right:.25rem;transition:transform .2s;color:var(--color-text-muted)}.expand-icon.expanded{transform:rotate(90deg)}.expanded-row td{padding:0!important;background:var(--color-bg-secondary);border-bottom:2px solid var(--color-border)}.course-detail-panel{padding:1rem;display:flex;flex-direction:column;gap:1rem}.course-detail-loading,.course-detail-error{display:flex;align-items:center;justify-content:center;gap:.75rem;padding:2rem;color:var(--color-text-muted)}.course-detail-error{color:var(--color-error);flex-direction:column}.course-detail-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem}.tile-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:1rem;padding-bottom:.75rem;border-bottom:1px solid var(--color-border)}.tile-header h3{font-size:1rem;font-weight:600;margin:0}.btn-close{background:none;border:none;font-size:1.25rem;color:var(--color-text-muted);cursor:pointer;padding:.25rem;line-height:1;border-radius:var(--radius);transition:all .15s}.btn-close:hover{background:var(--color-bg-secondary);color:var(--color-text)}.detail-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:.75rem 1.5rem}.detail-item{display:flex;flex-direction:column;gap:.125rem}.detail-item.span-2{grid-column:span 2}.detail-label{font-size:.7rem;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.025em}.detail-value{font-size:.875rem;color:var(--color-text)}.detail-value.mono{font-family:SF Mono,Monaco,Consolas,monospace;font-size:.75rem;background:var(--color-bg-secondary);padding:.125rem .375rem;border-radius:4px}.detail-value.description{line-height:1.4;max-height:4.2em;overflow:hidden;text-overflow:ellipsis}.thin-pack-row{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem}.thin-pack-tile{background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:1rem;border-left:4px solid}.thin-pack-tile.public{border-left-color:var(--color-success)}.thin-pack-tile.private{border-left-color:var(--color-primary)}.thin-pack-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:.75rem}.thin-pack-icon{font-size:1.5rem}.thin-pack-titles{flex:1}.thin-pack-title{font-size:.9rem;font-weight:600;margin:0}.thin-pack-subtitle{font-size:.75rem;color:var(--color-text-muted);margin:.125rem 0 0}.thin-pack-content{display:flex;flex-direction:column;gap:.5rem}.thin-pack-error{font-size:.75rem;color:var(--color-error);padding:.5rem;background:#ef44441a;border-radius:4px}.thin-pack-status{display:flex;align-items:center;gap:.5rem;font-size:.8rem}.thin-pack-status.exists{color:var(--color-success)}.thin-pack-status.exists .status-icon{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;background:var(--color-success);color:#fff;border-radius:50%;font-size:.7rem}.thin-pack-status.missing{color:var(--color-text-muted);font-style:italic}.thin-pack-meta{display:flex;gap:1rem;font-size:.7rem;color:var(--color-text-muted)}.thin-pack-actions{display:flex;gap:.5rem;margin-top:.25rem}.thin-pack-slug-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-slug-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-slug-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-slug-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-slug-input input::placeholder{color:var(--color-text-muted);opacity:.6}.thin-pack-name-input{display:flex;flex-direction:column;gap:.25rem}.thin-pack-name-input label{font-size:.7rem;color:var(--color-text-muted)}.thin-pack-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.thin-pack-name-input input:focus{outline:none;border-color:var(--color-primary)}.thin-pack-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.token-preview{font-size:.65rem;color:var(--color-text-muted);font-family:monospace;word-break:break-all}.thin-pack-visibility{display:flex;flex-direction:column;gap:.25rem;padding:.5rem;background:var(--color-bg-tertiary);border-radius:4px}.visibility-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.visibility-toggle input[type=checkbox]{width:16px;height:16px;cursor:pointer}.toggle-label{font-size:.85rem;font-weight:500}.visibility-hint{font-size:.7rem;color:var(--color-text-muted);margin-left:24px}.thin-pack-tile.lms{border-left-color:var(--color-success)}.thin-pack-tile.shared{border-left-color:var(--color-primary)}.upload-modal{width:600px;max-width:90vw;max-height:90vh;display:flex;flex-direction:column}.upload-modal-content{flex:1;overflow-y:auto;padding:1rem;display:flex;flex-direction:column;gap:1rem}.upload-modal .dropzone{min-height:120px}.upload-file-list{display:flex;flex-direction:column;gap:.5rem}.upload-file-list h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-file-item{display:flex;justify-content:space-between;align-items:center;padding:.5rem;background:var(--color-bg-secondary);border-radius:8px}.upload-file-info{display:flex;flex-direction:column;gap:.125rem}.upload-file-name{font-weight:500;font-size:.875rem}.upload-file-size{font-size:.75rem;color:var(--color-text-muted)}.upload-file-status{display:flex;align-items:center;gap:.5rem}.upload-progress-container{display:flex;align-items:center;gap:.75rem;min-width:150px}.upload-progress-bar{flex:1;height:8px;background:var(--color-border);border-radius:4px;overflow:hidden}.upload-progress-fill{height:100%;background:var(--color-primary);transition:width .15s ease-out}.upload-progress-text{font-size:.75rem;font-weight:500;color:var(--color-text-muted);min-width:36px;text-align:right}.status-processing{color:var(--color-primary);font-size:.875rem}.upload-link-options{background:var(--color-bg-secondary);border-radius:var(--radius);padding:1rem;display:flex;flex-direction:column;gap:.75rem}.upload-link-options h4{margin:0;font-size:.85rem;color:var(--color-text-muted)}.upload-link-option{display:flex;flex-direction:column;gap:.5rem}.upload-link-toggle{display:flex;align-items:center;gap:.5rem;cursor:pointer}.upload-link-toggle input[type=checkbox]{width:1rem;height:1rem;cursor:pointer}.upload-link-toggle span{font-size:.875rem}.upload-link-name-input{display:flex;flex-direction:column;gap:.25rem;padding-left:1.5rem}.upload-link-name-input input{padding:.375rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.upload-link-name-input input:focus{outline:none;border-color:var(--color-primary)}.upload-link-name-input input::placeholder{color:var(--color-text-muted);opacity:.6}.slug-preview{font-size:.7rem;color:var(--color-text-muted);font-family:monospace}.modal-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:1rem;border-top:1px solid var(--color-border)}@media (max-width: 768px){.detail-grid{grid-template-columns:1fr}.detail-item.span-2{grid-column:span 1}.thin-pack-row{grid-template-columns:1fr}}.user-menu{display:flex;align-items:center;gap:.75rem;margin-left:auto}.user-info{display:flex;align-items:center;gap:.5rem}.user-avatar{width:2rem;height:2rem;border-radius:50%;background:var(--color-primary);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:600;font-size:.875rem}.user-name{font-size:.875rem;color:var(--color-text);max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.header{display:flex;align-items:center;gap:1.5rem}.header .nav{margin-right:auto}.course-structure-step{display:flex;flex-direction:column;gap:.5rem}.course-structure-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:.25rem}.course-structure-header h4{margin:0;font-size:1rem;font-weight:600}.course-structure-meta{display:flex;align-items:center;gap:.5rem;font-size:.75rem;color:var(--color-text-muted)}.course-structure-badge{background:#2563eb1a;color:var(--color-primary);padding:.125rem .5rem;border-radius:999px;font-size:.7rem;font-weight:500}.tree-node{padding:.25rem 0}.tree-node-row{display:flex;align-items:center;gap:.375rem;min-height:1.5rem}.tree-node-icon{font-size:1rem;flex-shrink:0}.tree-node-label{font-weight:500;font-size:.875rem;flex:1}.tree-class-input{width:100%;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);margin-top:.25rem}.tree-class-input:focus{outline:none;border-color:var(--color-primary)}.tree-class-input::placeholder{color:var(--color-text-muted);opacity:.6}.tree-class-input-sm{max-width:200px;margin-top:0;margin-left:auto;flex-shrink:0}.tree-course{background:var(--color-bg-secondary);border:1px solid var(--color-border);border-radius:var(--radius);padding:.5rem .75rem}.tree-lessons{display:flex;flex-direction:column;gap:.125rem;max-height:400px;overflow-y:auto;border:1px solid var(--color-border);border-radius:var(--radius);padding:.25rem}.tree-lesson{border-bottom:1px solid var(--color-border)}.tree-lesson:last-child{border-bottom:none}.tree-node-lesson{cursor:pointer;padding:.375rem .5rem;border-radius:var(--radius-sm);transition:background .15s}.tree-node-lesson:hover{background:var(--color-bg-secondary)}.tree-expand{font-size:.625rem;width:1rem;text-align:center;color:var(--color-text-muted);flex-shrink:0}.tree-lesson-num{color:var(--color-text-muted);font-weight:600;font-size:.75rem}.tree-node-indicator{font-size:.65rem;color:var(--color-primary);background:#2563eb1a;padding:.0625rem .375rem;border-radius:999px;white-space:nowrap;flex-shrink:0}.tree-lesson-content{padding:.25rem .5rem .5rem 1.5rem}.tree-lesson-class{margin-bottom:.375rem}.tree-node-block{display:flex;align-items:center;gap:.375rem;padding:.1875rem 0}.tree-node-block .tree-node-row{flex:1;min-width:0}.tree-block-pipe{font-family:monospace;color:var(--color-border);font-size:.75rem;flex-shrink:0}.tree-block-type{font-size:.75rem;font-weight:500;color:var(--color-text);white-space:nowrap}.tree-block-title{font-size:.7rem;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}.structure-parsing-indicator{display:flex;align-items:center;gap:.75rem;padding:.75rem;color:var(--color-primary);font-size:.875rem}.structure-detected-badge{background:#10b9811a;color:var(--color-success);border:1px solid var(--color-success);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem}.structure-multi-note{font-size:.75rem;color:var(--color-text-muted);font-style:italic;padding:.25rem 0}.structure-summary-badge{background:#2563eb1a;color:var(--color-primary);border-radius:var(--radius);padding:.5rem .75rem;font-size:.8rem;margin-top:.25rem}.structure-section{display:flex;flex-direction:column}.structure-toggle-btn{display:flex;align-items:center;gap:.5rem;width:100%;padding:.625rem .75rem;background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);cursor:pointer;font-size:.875rem;font-weight:500;color:var(--color-text);transition:all .2s}.structure-toggle-btn:hover:not(:disabled){border-color:var(--color-primary);background:#2563eb08}.structure-toggle-btn.open{border-color:var(--color-primary);border-bottom-left-radius:0;border-bottom-right-radius:0;background:#2563eb0d}.structure-toggle-btn.disabled{opacity:.5;cursor:not-allowed;color:var(--color-text-muted)}.structure-toggle-btn .spinner{width:16px;height:16px;flex-shrink:0}.structure-toggle-arrow{margin-left:auto;font-size:.625rem;color:var(--color-text-muted);transition:transform .2s}.structure-panel{border:1px solid var(--color-primary);border-top:none;border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius);padding:.75rem;background:var(--color-bg);animation:panelSlideDown .15s ease-out}@keyframes panelSlideDown{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.block-cards{display:flex;flex-direction:column;gap:3px;padding:.375rem 0}.block-card{display:flex;flex-direction:column;gap:.25rem;padding:.5rem .625rem;border-radius:var(--radius-sm);border:1px solid var(--color-border);border-left:4px solid var(--block-accent, #6b7280);background:var(--color-bg);cursor:pointer;transition:all .15s}.block-card:hover:not(.grouped){border-color:var(--color-primary);border-left-color:var(--block-accent, #6b7280);background:#2563eb08}.block-card.selected{background:#2563eb14;border-color:var(--color-primary);border-left-color:var(--block-accent, #6b7280);box-shadow:0 0 0 1px #2563eb33}.block-card.grouped{cursor:default;opacity:.75;background:var(--color-bg-secondary)}.block-card-header{display:flex;align-items:center;gap:.5rem;min-height:1.25rem}.block-card-type{font-size:.8rem;font-weight:600;color:var(--color-text);white-space:nowrap}.block-card-title{font-size:.75rem;color:var(--color-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.block-card-check{color:var(--color-primary);font-weight:700;font-size:.875rem;flex-shrink:0;margin-left:auto}.block-card .tree-class-input-sm{margin-left:0;max-width:100%;margin-top:0}.block-group-container{border:2px dashed var(--color-primary);border-radius:var(--radius);padding:.375rem;margin:.25rem 0;background:#2563eb05}.block-group-container .block-card{border-left-width:3px}.bgp-group-header{display:flex;align-items:center;gap:.5rem;padding:.25rem .5rem;font-size:.7rem;border-bottom:1px solid rgba(37,99,235,.15);margin-bottom:.25rem}.bgp-group-label{font-weight:600;color:var(--color-primary)}.bgp-group-class{color:var(--color-text-muted);font-family:monospace;font-size:.65rem}.bgp-btn{background:none;border:none;font-size:.65rem;cursor:pointer;padding:.125rem .375rem;border-radius:var(--radius-sm);transition:all .15s}.bgp-btn-edit{color:var(--color-text-muted);margin-left:auto}.bgp-btn-edit:hover{color:var(--color-primary);background:#2563eb1a}.bgp-btn-ungroup{color:var(--color-text-muted)}.bgp-btn-ungroup:hover{color:var(--color-error);background:#ef44441a}.bgp-btn-save{color:var(--color-success)}.bgp-btn-save:hover{background:#10b9811a}.bgp-btn-cancel{color:var(--color-text-muted)}.bgp-btn-cancel:hover{background:var(--color-bg-secondary)}.bgp-group-edit{display:flex;align-items:center;gap:.375rem;flex:1}.bgp-edit-input{padding:.1875rem .375rem;font-size:.7rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text);flex:1}.bgp-edit-input:focus{outline:none;border-color:var(--color-primary)}.bgp-edit-input-sm{max-width:120px}.bgp-create{padding:.5rem .75rem;margin-top:.375rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg-secondary);display:flex;flex-direction:column;gap:.375rem;position:sticky;bottom:0;z-index:1}.bgp-create-info{font-size:.75rem;font-weight:500;color:var(--color-text)}.bgp-validation-error{color:var(--color-error);font-weight:400}.bgp-create-inputs{display:flex;gap:.375rem}.bgp-create-class{flex:2;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-class:focus{outline:none;border-color:var(--color-primary)}.bgp-create-label{flex:1;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.bgp-create-label:focus{outline:none;border-color:var(--color-primary)}.bgp-create-actions{display:flex;gap:.375rem}.bgp-hint{padding:.75rem;font-size:.75rem;color:var(--color-text-muted);text-align:center;font-style:italic}.upload-modal.preview-mode{width:95vw;max-width:95vw;height:90vh;max-height:90vh;display:flex;flex-direction:column}.upload-modal.preview-mode .modal-header{flex-shrink:0}.preview-split{display:flex;flex:1;overflow:hidden;border-top:1px solid var(--color-border)}.preview-split-left{width:380px;min-width:320px;overflow-y:auto;border-right:1px solid var(--color-border);padding:.75rem}.preview-split-left .tree-lessons{max-height:none;border:none;padding:0}.preview-split-right{flex:1;position:relative;background:var(--color-bg-secondary)}.preview-iframe{width:100%;height:100%;border:none}.preview-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:.75rem;color:var(--color-text-muted);font-size:.875rem}.preview-footer{flex-shrink:0;align-items:center;flex-wrap:wrap;position:relative}.preview-options-toggle{margin-left:.5rem}.preview-options-toggle.open{background:var(--color-bg-secondary)}.preview-options-panel{position:absolute;bottom:100%;left:50%;transform:translate(-50%);background:var(--color-bg);border:1px solid var(--color-border);border-radius:var(--radius);padding:.75rem;box-shadow:var(--shadow-lg);display:flex;flex-direction:column;gap:.5rem;min-width:250px;margin-bottom:.5rem;z-index:10}.preview-options-panel .upload-link-toggle{font-size:.8rem}.preview-option-skin{display:flex;align-items:center;gap:.5rem;font-size:.8rem}.preview-option-label{color:var(--color-text-muted);white-space:nowrap}.preview-option-input{flex:1;padding:.25rem .5rem;font-size:.8rem;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-bg);color:var(--color-text)}.preview-option-input:focus{outline:none;border-color:var(--color-primary)}@media (max-width: 900px){.upload-modal.preview-mode{width:100vw;max-width:100vw;height:100vh;max-height:100vh;border-radius:0}.preview-split{flex-direction:column}.preview-split-left{width:100%;min-width:0;max-height:40vh;border-right:none;border-bottom:1px solid var(--color-border)}.preview-split-right{flex:1;min-height:200px}}
package/dist/index.html CHANGED
@@ -5,8 +5,8 @@
5
5
  <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <title>Package Uploader</title>
8
- <script type="module" crossorigin src="/assets/index-BZdhKDnY.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-BfowR104.css">
8
+ <script type="module" crossorigin src="/assets/index-CAZIUAbb.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DHoRoGws.css">
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@package-uploader/ui",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "React UI for uploading and browsing courses on LMS platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/api/client.ts CHANGED
@@ -294,6 +294,16 @@ export const api = {
294
294
  getLaunchUrl: (documentId: number) =>
295
295
  request<{ url: string }>('GET', `/documents/${documentId}/launch`),
296
296
 
297
+ // Preview: extract SCORM ZIP to temp dir with bridge script for live preview
298
+ startPreview: async (file: File): Promise<{ token: string; url: string }> => {
299
+ const formData = new FormData();
300
+ formData.append('file', file);
301
+ return request<{ token: string; url: string }>('POST', '/preview', formData);
302
+ },
303
+
304
+ stopPreview: (token: string) =>
305
+ request<{ success: boolean }>('DELETE', `/preview/${token}`),
306
+
297
307
  // Wrap & Download (PA-Patcher wrapping without CDS upload)
298
308
  wrapAndDownload: async (file: File, options?: { skin?: string; classMappings?: CourseClassMappings }): Promise<void> => {
299
309
  const formData = new FormData();
@@ -37,6 +37,7 @@ interface CourseStructureStepProps {
37
37
  structure: CourseStructure;
38
38
  classMappings: CourseClassMappings;
39
39
  onChange: (mappings: CourseClassMappings) => void;
40
+ onNavigateLesson?: (lessonId: string) => void;
40
41
  }
41
42
 
42
43
  /** Color palette for block families */
@@ -66,6 +67,7 @@ interface LessonNodeProps {
66
67
  onSetLessonClass: (lessonId: string, value: string) => void;
67
68
  onSetBlockClass: (blockId: string, value: string) => void;
68
69
  onGroupsChange: (lessonId: string, groups: BlockGroup[]) => void;
70
+ onNavigateLesson?: (lessonId: string) => void;
69
71
  }
70
72
 
71
73
  function LessonNode({
@@ -75,6 +77,7 @@ function LessonNode({
75
77
  onSetLessonClass,
76
78
  onSetBlockClass,
77
79
  onGroupsChange,
80
+ onNavigateLesson,
78
81
  }: LessonNodeProps) {
79
82
  const [isExpanded, setIsExpanded] = useState(false);
80
83
 
@@ -239,7 +242,10 @@ function LessonNode({
239
242
  <div className="tree-lesson">
240
243
  <div
241
244
  className="tree-node tree-node-lesson"
242
- onClick={() => setIsExpanded((prev) => !prev)}
245
+ onClick={() => {
246
+ setIsExpanded((prev) => !prev);
247
+ onNavigateLesson?.(lesson.id);
248
+ }}
243
249
  >
244
250
  <div className="tree-node-row">
245
251
  <span className="tree-expand">{isExpanded ? '\u25BC' : '\u25B6'}</span>
@@ -333,6 +339,7 @@ export default function CourseStructureStep({
333
339
  structure,
334
340
  classMappings,
335
341
  onChange,
342
+ onNavigateLesson,
336
343
  }: CourseStructureStepProps) {
337
344
  const setCourseClass = (value: string) => {
338
345
  onChange({ ...classMappings, course: value || undefined });
@@ -426,6 +433,7 @@ export default function CourseStructureStep({
426
433
  onSetLessonClass={setLessonClass}
427
434
  onSetBlockClass={setBlockClass}
428
435
  onGroupsChange={handleGroupsChange}
436
+ onNavigateLesson={onNavigateLesson}
429
437
  />
430
438
  ))}
431
439
  </div>
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useState, useRef, useEffect, useCallback } from 'react';
2
2
  import { api, type CourseClassMappings } from '../api/client';
3
3
  import DropZone from './DropZone';
4
4
  import CourseStructureStep, { type CourseStructure } from './CourseStructureStep';
@@ -31,9 +31,17 @@ export default function UploadModal({
31
31
  const [courseStructure, setCourseStructure] = useState<CourseStructure | null>(null);
32
32
  const [classMappings, setClassMappings] = useState<CourseClassMappings>({});
33
33
  const [parsingStructure, setParsingStructure] = useState(false);
34
- const [structurePanelOpen, setStructurePanelOpen] = useState(false);
35
34
  const [parseFailed, setParseFailed] = useState(false);
36
35
 
36
+ // Preview state
37
+ const [previewToken, setPreviewToken] = useState<string | null>(null);
38
+ const [previewUrl, setPreviewUrl] = useState<string | null>(null);
39
+ const [previewLoading, setPreviewLoading] = useState(false);
40
+ const iframeRef = useRef<HTMLIFrameElement>(null);
41
+
42
+ // Options panel (collapsed by default in preview mode)
43
+ const [optionsOpen, setOptionsOpen] = useState(false);
44
+
37
45
  // LMS Thin Pack options
38
46
  const [createThinPack, setCreateThinPack] = useState(true);
39
47
  const [thinPackName, setThinPackName] = useState('');
@@ -48,6 +56,35 @@ export default function UploadModal({
48
56
  // Wrap & Download mode
49
57
  const [wrapOnly, setWrapOnly] = useState(false);
50
58
 
59
+ const isPreviewMode = !!previewToken && !!previewUrl;
60
+
61
+ // Sync classMappings to iframe via postMessage
62
+ useEffect(() => {
63
+ if (!isPreviewMode || !iframeRef.current?.contentWindow) return;
64
+ iframeRef.current.contentWindow.postMessage(
65
+ { type: 'pa-class-mappings', classMappings },
66
+ '*'
67
+ );
68
+ }, [classMappings, isPreviewMode]);
69
+
70
+ // Navigate iframe to a lesson when user clicks lesson in tree
71
+ const handleNavigateLesson = useCallback((lessonId: string) => {
72
+ if (!iframeRef.current?.contentWindow) return;
73
+ iframeRef.current.contentWindow.postMessage(
74
+ { type: 'pa-navigate', lessonId },
75
+ '*'
76
+ );
77
+ }, []);
78
+
79
+ // Cleanup preview on unmount
80
+ useEffect(() => {
81
+ return () => {
82
+ if (previewToken) {
83
+ api.stopPreview(previewToken).catch(() => {});
84
+ }
85
+ };
86
+ }, [previewToken]);
87
+
51
88
  function generateSlug(name: string): string {
52
89
  return name.trim().replace(/\s+/g, '_');
53
90
  }
@@ -59,11 +96,15 @@ export default function UploadModal({
59
96
  }));
60
97
  setFiles((prev) => [...prev, ...fileItems]);
61
98
 
62
- // Reset structure state when files change
99
+ // Reset structure + preview state when files change
63
100
  setCourseStructure(null);
64
101
  setClassMappings({});
65
- setStructurePanelOpen(false);
66
102
  setParseFailed(false);
103
+ if (previewToken) {
104
+ api.stopPreview(previewToken).catch(() => {});
105
+ setPreviewToken(null);
106
+ setPreviewUrl(null);
107
+ }
67
108
  }
68
109
 
69
110
  function handleRemoveFile(index: number) {
@@ -72,17 +113,25 @@ export default function UploadModal({
72
113
  if (next.length !== 1) {
73
114
  setCourseStructure(null);
74
115
  setClassMappings({});
75
- setStructurePanelOpen(false);
76
116
  setParseFailed(false);
117
+ if (previewToken) {
118
+ api.stopPreview(previewToken).catch(() => {});
119
+ setPreviewToken(null);
120
+ setPreviewUrl(null);
121
+ }
77
122
  }
78
123
  return next;
79
124
  });
80
125
  }
81
126
 
82
127
  async function handleCustomizeClick() {
83
- // Already parsedjust toggle
84
- if (courseStructure) {
85
- setStructurePanelOpen((prev) => !prev);
128
+ // Already in preview mode toggle back to normal
129
+ if (isPreviewMode) {
130
+ if (previewToken) {
131
+ api.stopPreview(previewToken).catch(() => {});
132
+ }
133
+ setPreviewToken(null);
134
+ setPreviewUrl(null);
86
135
  return;
87
136
  }
88
137
 
@@ -93,19 +142,53 @@ export default function UploadModal({
93
142
  if (files.length !== 1) return;
94
143
 
95
144
  setParsingStructure(true);
145
+ setPreviewLoading(true);
146
+
96
147
  try {
97
- const structure = await api.parseCourseStructure(files[0].file);
148
+ // Parse structure and start preview in parallel
149
+ const [structure, preview] = await Promise.all([
150
+ courseStructure
151
+ ? Promise.resolve(courseStructure)
152
+ : api.parseCourseStructure(files[0].file),
153
+ api.startPreview(files[0].file),
154
+ ]);
155
+
98
156
  if (structure) {
99
157
  setCourseStructure(structure);
100
- setStructurePanelOpen(true);
101
158
  setParseFailed(false);
159
+ setPreviewToken(preview.token);
160
+ setPreviewUrl(preview.url);
102
161
  } else {
103
162
  setParseFailed(true);
163
+ // Cleanup the preview if structure parse failed
164
+ api.stopPreview(preview.token).catch(() => {});
104
165
  }
105
166
  } catch {
106
167
  setParseFailed(true);
107
168
  }
169
+
108
170
  setParsingStructure(false);
171
+ setPreviewLoading(false);
172
+ }
173
+
174
+ function handleClose() {
175
+ if (previewToken) {
176
+ api.stopPreview(previewToken).catch(() => {});
177
+ }
178
+ onClose();
179
+ }
180
+
181
+ // When iframe loads, send current classMappings
182
+ function handleIframeLoad() {
183
+ // Small delay to let bridge script initialize
184
+ setTimeout(() => {
185
+ if (iframeRef.current?.contentWindow) {
186
+ iframeRef.current.contentWindow.postMessage(
187
+ { type: 'pa-class-mappings', classMappings },
188
+ '*'
189
+ );
190
+ }
191
+ }, 500);
109
192
  }
110
193
 
111
194
  function formatSize(bytes: number): string {
@@ -249,18 +332,169 @@ export default function UploadModal({
249
332
  ? 'Analyzing...'
250
333
  : parseFailed
251
334
  ? 'Not a Rise course'
252
- : courseStructure
253
- ? `Customize Classes (${courseStructure.lessons.length} lessons)`
335
+ : isPreviewMode
336
+ ? 'Close Preview'
254
337
  : 'Customize Classes';
255
338
 
339
+ // Build the preview URL — needs the API base path prepended
340
+ const fullPreviewUrl = previewUrl
341
+ ? (() => {
342
+ // Detect API base same way client.ts does
343
+ const scripts = document.querySelectorAll('script[src*="assets/index"]');
344
+ if (scripts.length > 0) {
345
+ const src = (scripts[0] as HTMLScriptElement).src;
346
+ const url = new URL(src);
347
+ const assetsIndex = url.pathname.indexOf('/assets/');
348
+ if (assetsIndex > 0) {
349
+ return url.pathname.substring(0, assetsIndex) + '/' + previewUrl.replace(/^\//, '');
350
+ }
351
+ }
352
+ return previewUrl;
353
+ })()
354
+ : null;
355
+
356
+ // --- Preview mode: full-screen split layout ---
357
+ if (isPreviewMode && courseStructure) {
358
+ return (
359
+ <div className="modal-overlay" onClick={handleClose}>
360
+ <div className="modal upload-modal preview-mode" onClick={(e) => e.stopPropagation()}>
361
+ <div className="modal-header">
362
+ <h3 className="modal-title">
363
+ Customize Classes — {courseStructure.courseTitle}
364
+ </h3>
365
+ {assignmentCount > 0 && (
366
+ <span className="course-structure-badge">
367
+ {assignmentCount} class{assignmentCount !== 1 ? 'es' : ''} assigned
368
+ </span>
369
+ )}
370
+ <button className="modal-close" onClick={handleClose}>
371
+ x
372
+ </button>
373
+ </div>
374
+
375
+ <div className="preview-split">
376
+ {/* Left: Course structure controls */}
377
+ <div className="preview-split-left">
378
+ <CourseStructureStep
379
+ structure={courseStructure}
380
+ classMappings={classMappings}
381
+ onChange={setClassMappings}
382
+ onNavigateLesson={handleNavigateLesson}
383
+ />
384
+ </div>
385
+
386
+ {/* Right: Live SCORM preview */}
387
+ <div className="preview-split-right">
388
+ {previewLoading ? (
389
+ <div className="preview-loading">
390
+ <div className="spinner" />
391
+ <span>Loading preview...</span>
392
+ </div>
393
+ ) : (
394
+ <iframe
395
+ ref={iframeRef}
396
+ src={fullPreviewUrl || ''}
397
+ className="preview-iframe"
398
+ title="Course Preview"
399
+ onLoad={handleIframeLoad}
400
+ sandbox="allow-scripts allow-same-origin allow-popups"
401
+ />
402
+ )}
403
+ </div>
404
+ </div>
405
+
406
+ <div className="modal-footer preview-footer">
407
+ <button className="btn btn-secondary" onClick={handleClose} disabled={uploading}>
408
+ Cancel
409
+ </button>
410
+
411
+ <button
412
+ className={`btn btn-sm btn-secondary preview-options-toggle${optionsOpen ? ' open' : ''}`}
413
+ onClick={() => setOptionsOpen((prev) => !prev)}
414
+ >
415
+ Options {optionsOpen ? '\u25B2' : '\u25BC'}
416
+ </button>
417
+
418
+ {optionsOpen && (
419
+ <div className="preview-options-panel">
420
+ <label className="upload-link-toggle">
421
+ <input
422
+ type="checkbox"
423
+ checked={wrapOnly}
424
+ onChange={(e) => setWrapOnly(e.target.checked)}
425
+ disabled={uploading}
426
+ />
427
+ <span>Wrap & Download only</span>
428
+ </label>
429
+
430
+ {!wrapOnly && (
431
+ <>
432
+ <label className="upload-link-toggle">
433
+ <input
434
+ type="checkbox"
435
+ checked={createThinPack}
436
+ onChange={(e) => setCreateThinPack(e.target.checked)}
437
+ disabled={uploading}
438
+ />
439
+ <span>LMS Thin Pack</span>
440
+ </label>
441
+ <label className="upload-link-toggle">
442
+ <input
443
+ type="checkbox"
444
+ checked={createSharedLink}
445
+ onChange={(e) => setCreateSharedLink(e.target.checked)}
446
+ disabled={uploading}
447
+ />
448
+ <span>Shared Link</span>
449
+ </label>
450
+ </>
451
+ )}
452
+
453
+ <div className="preview-option-skin">
454
+ <span className="preview-option-label">Skin:</span>
455
+ <input
456
+ type="text"
457
+ placeholder="e.g. marketing"
458
+ value={skinName}
459
+ onChange={(e) => setSkinName(e.target.value)}
460
+ disabled={uploading}
461
+ className="preview-option-input"
462
+ />
463
+ </div>
464
+ </div>
465
+ )}
466
+
467
+ <button
468
+ className="btn btn-primary"
469
+ onClick={handleUpload}
470
+ disabled={uploading || pendingCount === 0}
471
+ >
472
+ {uploading ? (
473
+ <>
474
+ <div className="spinner" />
475
+ {wrapOnly ? 'Wrapping...' : 'Uploading...'}
476
+ </>
477
+ ) : wrapOnly ? (
478
+ 'Wrap & Download'
479
+ ) : (
480
+ 'Upload'
481
+ )}
482
+ </button>
483
+ </div>
484
+ </div>
485
+ </div>
486
+ );
487
+ }
488
+
489
+ // --- Normal mode: standard upload modal ---
256
490
  return (
257
- <div className="modal-overlay" onClick={onClose}>
491
+ <div className="modal-overlay" onClick={handleClose}>
258
492
  <div className="modal upload-modal" onClick={(e) => e.stopPropagation()}>
259
493
  <div className="modal-header">
260
494
  <h3 className="modal-title">
261
495
  {wrapOnly ? 'Wrap & Download' : `Upload to: ${folderName}`}
262
496
  </h3>
263
- <button className="modal-close" onClick={onClose}>
497
+ <button className="modal-close" onClick={handleClose}>
264
498
  x
265
499
  </button>
266
500
  </div>
@@ -308,35 +542,20 @@ export default function UploadModal({
308
542
  </div>
309
543
  )}
310
544
 
311
- {/* Customize Classes on-demand toggle */}
545
+ {/* Customize Classes button */}
312
546
  {isSingleFile && !uploading && (
313
547
  <div className="structure-section">
314
548
  <button
315
- className={`structure-toggle-btn${structurePanelOpen ? ' open' : ''}${parseFailed ? ' disabled' : ''}`}
549
+ className={`structure-toggle-btn${parseFailed ? ' disabled' : ''}`}
316
550
  onClick={handleCustomizeClick}
317
551
  disabled={parsingStructure || parseFailed}
318
552
  >
319
553
  {parsingStructure && <div className="spinner" />}
320
554
  <span>{customizeLabel}</span>
321
- {!parsingStructure && !parseFailed && (
322
- <span className="structure-toggle-arrow">
323
- {structurePanelOpen ? '\u25B2' : '\u25BC'}
324
- </span>
325
- )}
326
- {assignmentCount > 0 && !structurePanelOpen && (
555
+ {assignmentCount > 0 && (
327
556
  <span className="course-structure-badge">{assignmentCount} assigned</span>
328
557
  )}
329
558
  </button>
330
-
331
- {structurePanelOpen && courseStructure && (
332
- <div className="structure-panel">
333
- <CourseStructureStep
334
- structure={courseStructure}
335
- classMappings={classMappings}
336
- onChange={setClassMappings}
337
- />
338
- </div>
339
- )}
340
559
  </div>
341
560
  )}
342
561
 
@@ -432,7 +651,7 @@ export default function UploadModal({
432
651
  </div>
433
652
 
434
653
  {/* Class assignments summary */}
435
- {assignmentCount > 0 && !structurePanelOpen && (
654
+ {assignmentCount > 0 && (
436
655
  <div className="structure-summary-badge">
437
656
  Class assignments:{' '}
438
657
  {[
@@ -454,7 +673,7 @@ export default function UploadModal({
454
673
  </div>
455
674
 
456
675
  <div className="modal-footer">
457
- <button className="btn btn-secondary" onClick={onClose} disabled={uploading}>
676
+ <button className="btn btn-secondary" onClick={handleClose} disabled={uploading}>
458
677
  Cancel
459
678
  </button>
460
679
  <button