@package-uploader/ui 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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-BtMbH61W.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.5",
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,18 @@ export const api = {
294
294
  getLaunchUrl: (documentId: number) =>
295
295
  request<{ url: string }>('GET', `/documents/${documentId}/launch`),
296
296
 
297
+ // Preview: patch + extract SCORM ZIP to temp dir with bridge script for live preview
298
+ startPreview: async (file: File, options?: { skin?: string; classMappings?: CourseClassMappings }): Promise<{ token: string; url: string }> => {
299
+ const formData = new FormData();
300
+ formData.append('file', file);
301
+ if (options?.skin) formData.append('skin', options.skin);
302
+ if (options?.classMappings) formData.append('classMappings', JSON.stringify(options.classMappings));
303
+ return request<{ token: string; url: string }>('POST', '/preview', formData);
304
+ },
305
+
306
+ stopPreview: (token: string) =>
307
+ request<{ success: boolean }>('DELETE', `/preview/${token}`),
308
+
297
309
  // Wrap & Download (PA-Patcher wrapping without CDS upload)
298
310
  wrapAndDownload: async (file: File, options?: { skin?: string; classMappings?: CourseClassMappings }): Promise<void> => {
299
311
  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,31 @@ 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
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);
135
+ return;
136
+ }
137
+
138
+ // Already parsed but no preview — toggle panel off (clear structure to collapse)
84
139
  if (courseStructure) {
85
- setStructurePanelOpen((prev) => !prev);
140
+ setCourseStructure(null);
86
141
  return;
87
142
  }
88
143
 
@@ -93,19 +148,63 @@ export default function UploadModal({
93
148
  if (files.length !== 1) return;
94
149
 
95
150
  setParsingStructure(true);
96
- try {
97
- const structure = await api.parseCourseStructure(files[0].file);
98
- if (structure) {
99
- setCourseStructure(structure);
100
- setStructurePanelOpen(true);
101
- setParseFailed(false);
102
- } else {
103
- setParseFailed(true);
151
+ setPreviewLoading(true);
152
+
153
+ // Parse structure first — this determines if we can proceed
154
+ let structure = courseStructure;
155
+ if (!structure) {
156
+ try {
157
+ structure = await api.parseCourseStructure(files[0].file);
158
+ } catch {
159
+ structure = null;
104
160
  }
105
- } catch {
161
+ }
162
+
163
+ if (!structure) {
106
164
  setParseFailed(true);
165
+ setParsingStructure(false);
166
+ setPreviewLoading(false);
167
+ return;
107
168
  }
169
+
170
+ setCourseStructure(structure);
171
+ setParseFailed(false);
108
172
  setParsingStructure(false);
173
+
174
+ // Start preview independently — patches with current skin + classMappings
175
+ try {
176
+ const previewOptions: { skin?: string; classMappings?: CourseClassMappings } = {};
177
+ if (skinName.trim()) previewOptions.skin = skinName.trim();
178
+ if (Object.keys(classMappings).length > 0) previewOptions.classMappings = classMappings;
179
+ const preview = await api.startPreview(files[0].file, previewOptions);
180
+ setPreviewToken(preview.token);
181
+ setPreviewUrl(preview.url);
182
+ } catch (err) {
183
+ console.warn('Preview not available:', err);
184
+ // Preview failed but structure is valid — stay in non-preview mode
185
+ }
186
+
187
+ setPreviewLoading(false);
188
+ }
189
+
190
+ function handleClose() {
191
+ if (previewToken) {
192
+ api.stopPreview(previewToken).catch(() => {});
193
+ }
194
+ onClose();
195
+ }
196
+
197
+ // When iframe loads, send current classMappings
198
+ function handleIframeLoad() {
199
+ // Small delay to let bridge script initialize
200
+ setTimeout(() => {
201
+ if (iframeRef.current?.contentWindow) {
202
+ iframeRef.current.contentWindow.postMessage(
203
+ { type: 'pa-class-mappings', classMappings },
204
+ '*'
205
+ );
206
+ }
207
+ }, 500);
109
208
  }
110
209
 
111
210
  function formatSize(bytes: number): string {
@@ -249,18 +348,171 @@ export default function UploadModal({
249
348
  ? 'Analyzing...'
250
349
  : parseFailed
251
350
  ? 'Not a Rise course'
351
+ : isPreviewMode
352
+ ? 'Close Preview'
252
353
  : courseStructure
253
354
  ? `Customize Classes (${courseStructure.lessons.length} lessons)`
254
355
  : 'Customize Classes';
255
356
 
357
+ // Build the preview URL — needs the API base path prepended
358
+ const fullPreviewUrl = previewUrl
359
+ ? (() => {
360
+ // Detect API base same way client.ts does
361
+ const scripts = document.querySelectorAll('script[src*="assets/index"]');
362
+ if (scripts.length > 0) {
363
+ const src = (scripts[0] as HTMLScriptElement).src;
364
+ const url = new URL(src);
365
+ const assetsIndex = url.pathname.indexOf('/assets/');
366
+ if (assetsIndex > 0) {
367
+ return url.pathname.substring(0, assetsIndex) + '/' + previewUrl.replace(/^\//, '');
368
+ }
369
+ }
370
+ return previewUrl;
371
+ })()
372
+ : null;
373
+
374
+ // --- Preview mode: full-screen split layout ---
375
+ if (isPreviewMode && courseStructure) {
376
+ return (
377
+ <div className="modal-overlay" onClick={handleClose}>
378
+ <div className="modal upload-modal preview-mode" onClick={(e) => e.stopPropagation()}>
379
+ <div className="modal-header">
380
+ <h3 className="modal-title">
381
+ Customize Classes — {courseStructure.courseTitle}
382
+ </h3>
383
+ {assignmentCount > 0 && (
384
+ <span className="course-structure-badge">
385
+ {assignmentCount} class{assignmentCount !== 1 ? 'es' : ''} assigned
386
+ </span>
387
+ )}
388
+ <button className="modal-close" onClick={handleClose}>
389
+ x
390
+ </button>
391
+ </div>
392
+
393
+ <div className="preview-split">
394
+ {/* Left: Course structure controls */}
395
+ <div className="preview-split-left">
396
+ <CourseStructureStep
397
+ structure={courseStructure}
398
+ classMappings={classMappings}
399
+ onChange={setClassMappings}
400
+ onNavigateLesson={handleNavigateLesson}
401
+ />
402
+ </div>
403
+
404
+ {/* Right: Live SCORM preview */}
405
+ <div className="preview-split-right">
406
+ {previewLoading ? (
407
+ <div className="preview-loading">
408
+ <div className="spinner" />
409
+ <span>Loading preview...</span>
410
+ </div>
411
+ ) : (
412
+ <iframe
413
+ ref={iframeRef}
414
+ src={fullPreviewUrl || ''}
415
+ className="preview-iframe"
416
+ title="Course Preview"
417
+ onLoad={handleIframeLoad}
418
+ sandbox="allow-scripts allow-same-origin allow-popups"
419
+ />
420
+ )}
421
+ </div>
422
+ </div>
423
+
424
+ <div className="modal-footer preview-footer">
425
+ <button className="btn btn-secondary" onClick={handleClose} disabled={uploading}>
426
+ Cancel
427
+ </button>
428
+
429
+ <button
430
+ className={`btn btn-sm btn-secondary preview-options-toggle${optionsOpen ? ' open' : ''}`}
431
+ onClick={() => setOptionsOpen((prev) => !prev)}
432
+ >
433
+ Options {optionsOpen ? '\u25B2' : '\u25BC'}
434
+ </button>
435
+
436
+ {optionsOpen && (
437
+ <div className="preview-options-panel">
438
+ <label className="upload-link-toggle">
439
+ <input
440
+ type="checkbox"
441
+ checked={wrapOnly}
442
+ onChange={(e) => setWrapOnly(e.target.checked)}
443
+ disabled={uploading}
444
+ />
445
+ <span>Wrap & Download only</span>
446
+ </label>
447
+
448
+ {!wrapOnly && (
449
+ <>
450
+ <label className="upload-link-toggle">
451
+ <input
452
+ type="checkbox"
453
+ checked={createThinPack}
454
+ onChange={(e) => setCreateThinPack(e.target.checked)}
455
+ disabled={uploading}
456
+ />
457
+ <span>LMS Thin Pack</span>
458
+ </label>
459
+ <label className="upload-link-toggle">
460
+ <input
461
+ type="checkbox"
462
+ checked={createSharedLink}
463
+ onChange={(e) => setCreateSharedLink(e.target.checked)}
464
+ disabled={uploading}
465
+ />
466
+ <span>Shared Link</span>
467
+ </label>
468
+ </>
469
+ )}
470
+
471
+ <div className="preview-option-skin">
472
+ <span className="preview-option-label">Skin:</span>
473
+ <input
474
+ type="text"
475
+ placeholder="e.g. marketing"
476
+ value={skinName}
477
+ onChange={(e) => setSkinName(e.target.value)}
478
+ disabled={uploading}
479
+ className="preview-option-input"
480
+ />
481
+ </div>
482
+ </div>
483
+ )}
484
+
485
+ <button
486
+ className="btn btn-primary"
487
+ onClick={handleUpload}
488
+ disabled={uploading || pendingCount === 0}
489
+ >
490
+ {uploading ? (
491
+ <>
492
+ <div className="spinner" />
493
+ {wrapOnly ? 'Wrapping...' : 'Uploading...'}
494
+ </>
495
+ ) : wrapOnly ? (
496
+ 'Wrap & Download'
497
+ ) : (
498
+ 'Upload'
499
+ )}
500
+ </button>
501
+ </div>
502
+ </div>
503
+ </div>
504
+ );
505
+ }
506
+
507
+ // --- Normal mode: standard upload modal ---
256
508
  return (
257
- <div className="modal-overlay" onClick={onClose}>
509
+ <div className="modal-overlay" onClick={handleClose}>
258
510
  <div className="modal upload-modal" onClick={(e) => e.stopPropagation()}>
259
511
  <div className="modal-header">
260
512
  <h3 className="modal-title">
261
513
  {wrapOnly ? 'Wrap & Download' : `Upload to: ${folderName}`}
262
514
  </h3>
263
- <button className="modal-close" onClick={onClose}>
515
+ <button className="modal-close" onClick={handleClose}>
264
516
  x
265
517
  </button>
266
518
  </div>
@@ -308,27 +560,27 @@ export default function UploadModal({
308
560
  </div>
309
561
  )}
310
562
 
311
- {/* Customize Classes on-demand toggle */}
563
+ {/* Customize Classes button + panel */}
312
564
  {isSingleFile && !uploading && (
313
565
  <div className="structure-section">
314
566
  <button
315
- className={`structure-toggle-btn${structurePanelOpen ? ' open' : ''}${parseFailed ? ' disabled' : ''}`}
567
+ className={`structure-toggle-btn${courseStructure && !isPreviewMode ? ' open' : ''}${parseFailed ? ' disabled' : ''}`}
316
568
  onClick={handleCustomizeClick}
317
569
  disabled={parsingStructure || parseFailed}
318
570
  >
319
571
  {parsingStructure && <div className="spinner" />}
320
572
  <span>{customizeLabel}</span>
321
- {!parsingStructure && !parseFailed && (
573
+ {!parsingStructure && !parseFailed && courseStructure && (
322
574
  <span className="structure-toggle-arrow">
323
- {structurePanelOpen ? '\u25B2' : '\u25BC'}
575
+ {courseStructure && !isPreviewMode ? '\u25B2' : '\u25BC'}
324
576
  </span>
325
577
  )}
326
- {assignmentCount > 0 && !structurePanelOpen && (
578
+ {assignmentCount > 0 && !courseStructure && (
327
579
  <span className="course-structure-badge">{assignmentCount} assigned</span>
328
580
  )}
329
581
  </button>
330
582
 
331
- {structurePanelOpen && courseStructure && (
583
+ {courseStructure && !isPreviewMode && (
332
584
  <div className="structure-panel">
333
585
  <CourseStructureStep
334
586
  structure={courseStructure}
@@ -432,7 +684,7 @@ export default function UploadModal({
432
684
  </div>
433
685
 
434
686
  {/* Class assignments summary */}
435
- {assignmentCount > 0 && !structurePanelOpen && (
687
+ {assignmentCount > 0 && (
436
688
  <div className="structure-summary-badge">
437
689
  Class assignments:{' '}
438
690
  {[
@@ -454,7 +706,7 @@ export default function UploadModal({
454
706
  </div>
455
707
 
456
708
  <div className="modal-footer">
457
- <button className="btn btn-secondary" onClick={onClose} disabled={uploading}>
709
+ <button className="btn btn-secondary" onClick={handleClose} disabled={uploading}>
458
710
  Cancel
459
711
  </button>
460
712
  <button