dsh-plugin-lookatstudy 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -301,6 +301,156 @@ window.__ModuleLoader__.load({
301
301
  .lks-ui .lks14-search{background:rgb(0 0 0/0.25);border:1px solid rgb(255 255 255/0.15);color:var(--ink-strong);border-radius:10px}
302
302
  .lks-ui .lks14-search:focus{border-color:var(--brand);outline:none}
303
303
  .lks-ui .lks14-search::placeholder{color:rgb(var(--ink-rgb)/0.35)}
304
+
305
+ /* ══════════ A-track (P10a): island zeroing — the 27+6 un-skinned leftovers ══════════ */
306
+ /* buttons reset to UA buttontext/buttonface (black/light) — the panel's ink must flow through */
307
+ .lks-ui button{color:inherit;background-color:transparent;border-color:transparent}
308
+ /* A6 token completion: rgb-channel companions + concept-map palette (upstream :root) */
309
+ .lks-ui{--brand-dark-rgb:70 163 2;--brand-light-rgb:126 217 87;
310
+ --accent-dark-rgb:10 140 220;--accent-light-rgb:94 212 255;
311
+ --gold-dark-rgb:217 158 0;--gold-light-rgb:255 230 128;
312
+ --warning-dark-rgb:230 43 43;--warning-light-rgb:255 117 117;
313
+ --exam-dark-rgb:126 34 206;--exam-light-rgb:192 132 252;
314
+ --cm-c0-fill:#1C3352;--cm-c0-line:#6F9FD4;--cm-c1-fill:#1F3A29;--cm-c1-line:#7FB37F;
315
+ --cm-c2-fill:#42301A;--cm-c2-line:#CFA05E;--cm-c3-fill:#3D3620;--cm-c3-line:#CBB75E;
316
+ --cm-c4-fill:#33294A;--cm-c4-line:#A48DCB}
317
+ /* A1 in-chat quiz options (click = send answer; neutral pill, hover brand) */
318
+ .lks-ui .lks14-opt{background:rgb(var(--ink-rgb)/0.05);border:2px solid transparent;color:var(--ink)}
319
+ .lks-ui .lks14-opt:hover{border-color:var(--brand);background:rgb(var(--ink-rgb)/0.05)}
320
+ .lks-ui .lks14-optletter{color:var(--brand-light)}
321
+ .lks-ui .lks14-starter{background:transparent;border:1px solid var(--border-faint);color:var(--ink-muted)}
322
+ .lks-ui .lks14-starter:hover{background:rgb(var(--ink-rgb)/0.06);color:var(--ink);border-color:var(--border)}
323
+ .lks-ui .lks14-chatlesson{color:var(--ink-muted)}
324
+ .lks-ui .lks14-meta{color:var(--ink-muted)}
325
+ .lks-ui .lks14-dueitem{color:var(--ink-muted)}
326
+ .lks-ui .lks14-hint{color:var(--ink-faint)}
327
+ .lks-ui .lks-readbar-notice{color:var(--ink-faint)}
328
+ .lks-ui .lks-cmap-legend{color:var(--ink-muted)}
329
+ /* A2 quiz-card body family */
330
+ .lks-ui .lks-qcard-prompt{color:var(--ink)}
331
+ .lks-ui .lks-qcard-count{color:var(--ink-faint)}
332
+ .lks-ui .lks-qcard-ans{color:var(--ink-faint)}
333
+ .lks-ui .lks-qcard-expl{color:var(--ink-muted);border-left-color:var(--accent)}
334
+ .lks-ui .lks-qcard-prompt.review.wrong{color:var(--warning-light)}
335
+ .lks-ui .lks-qcard-q.review{border-bottom-color:var(--border)}
336
+ /* A2 artifact bits */
337
+ .lks-ui .lks-acard-expand{color:var(--ink-faint);border:none;background:none}
338
+ .lks-ui .lks-acard-expand:hover{color:var(--ink);background:rgb(var(--ink-rgb)/0.06)}
339
+ .lks-ui .lks-acard-modal-title{color:var(--ink-strong)}
340
+ .lks-ui .lks-acard-guess-prompt{color:var(--ink)}
341
+ .lks-ui .lks-acard-guess-wait{color:var(--ink-faint)}
342
+ .lks-ui .lks-acard-note b{color:var(--ink-strong)}
343
+ .lks-ui .lks-acard-stage{border-top-color:var(--border)}
344
+ /* A2 note family */
345
+ .lks-ui .lks-note-text{color:var(--ink-muted)}
346
+ .lks-ui .lks-note-text th,.lks-ui .lks-note-text td{border-color:var(--border)}
347
+ .lks-ui .lks-note-src{color:var(--ink-faint)}
348
+ .lks-ui .lks-note-del{color:var(--ink-faint)}
349
+ .lks-ui .lks-note-del:hover{color:var(--warning-light);background:rgb(var(--warning-rgb)/0.08)}
350
+ .lks-ui .lks-note-del.armed{color:#fff;background:var(--warning)}
351
+ .lks-ui .lks-note-q{color:var(--ink-muted)}
352
+ .lks-ui .lks-note-title{color:var(--ink-strong)}
353
+ /* A3 selection popover → surface-0 floating card */
354
+ .lks-ui .lks-quote-btn{background:var(--surface-0);border:1px solid var(--border);box-shadow:0 4px 12px -2px rgb(var(--shadow-rgb)/0.14),0 1px 3px -1px rgb(var(--shadow-rgb)/0.08);border-radius:10px;overflow:hidden;animation:lks-confirm-enter 160ms var(--ease-out-back)}
355
+ .lks-ui .lks-quote-btn button{color:var(--ink)}
356
+ .lks-ui .lks-quote-btn button:hover{background:rgb(var(--ink-rgb)/0.06)}
357
+ .lks-ui .lks-quote-btn button + button{border-left-color:var(--border)}
358
+ /* A4 prose links / markers / strong / hr */
359
+ .lks-ui .lks14-prose a,.lks-ui .lks-note-text a{color:rgb(var(--accent-rgb));text-decoration:none;font-weight:500}
360
+ .lks-ui .lks14-prose a:hover,.lks-ui .lks-note-text a:hover{text-decoration:underline}
361
+ .lks-ui .lks14-prose li::marker,.lks-ui .lks-note-text li::marker{color:rgb(var(--brand-rgb))}
362
+ .lks-ui .lks14-prose strong,.lks-ui .lks-note-text strong{color:var(--ink-strong)}
363
+ .lks-ui .lks14-prose hr,.lks-ui .lks-note-text hr{border-color:var(--border)}
364
+ /* A8 table borders inside bubbles/cards/notes */
365
+ .lks-ui .lks14-msg-assistant th,.lks-ui .lks14-msg-assistant td{border-color:var(--border)}
366
+ .lks-ui .lks-acard-table th,.lks-ui .lks-acard-table td{border-color:var(--border)}
367
+ .lks-ui .lks-acard-table th{border-bottom-color:var(--brand)}
368
+ /* A5 scoped scrollbar (host-light chrome otherwise scrolls inside dark columns) */
369
+ .lks-ui ::-webkit-scrollbar{width:8px;height:8px}
370
+ .lks-ui ::-webkit-scrollbar-track{background:transparent}
371
+ .lks-ui ::-webkit-scrollbar-thumb{background:var(--border);border-radius:9999px;border:2px solid transparent;background-clip:padding-box}
372
+ .lks-ui ::-webkit-scrollbar-thumb:hover{background:var(--surface-3);background-clip:padding-box}
373
+ /* A7 feedback/ambient animations (upstream values verbatim) */
374
+ @keyframes lks-crown-sparkle{0%,100%{transform:scale(1);filter:drop-shadow(0 0 0 var(--gold))}50%{transform:scale(1.08);filter:drop-shadow(0 0 6px var(--gold-glow))}}
375
+ .lks-ui .lks-bubble-mastered .lks-bubble-glyph{animation:lks-crown-sparkle 1.6s ease-in-out infinite;display:inline-flex}
376
+ @keyframes lks-energy-breathe{0%,100%{opacity:.78;filter:drop-shadow(0 0 0 var(--gold))}50%{opacity:1;filter:drop-shadow(0 0 4px var(--gold-glow))}}
377
+ @keyframes lks-flame-flicker{0%,100%{transform:scale(1) rotate(-2deg)}25%{transform:scale(1.06) rotate(1deg)}50%{transform:scale(.97) rotate(-1deg)}75%{transform:scale(1.04) rotate(2deg)}}
378
+ .lks-ui .lks-hdr-xp .lks-hdr-glyph{display:inline-flex;animation:lks-energy-breathe 2s ease-in-out infinite}
379
+ .lks-ui .lks-hdr-streak .lks-hdr-glyph{display:inline-flex;animation:lks-flame-flicker 1.2s ease-in-out infinite}
380
+ @keyframes lks-answer-correct{0%{transform:scale(1)}40%{transform:scale(1.05)}100%{transform:scale(1)}}
381
+ @keyframes lks-answer-wrong{0%,100%{transform:translateX(0)}20%,60%{transform:translateX(-3px)}40%,80%{transform:translateX(3px)}}
382
+ .lks-ui .lks-qcard-opt.right{animation:lks-answer-correct 350ms var(--ease-spring)}
383
+ .lks-ui .lks-qcard-opt.wrong{animation:lks-answer-wrong 320ms ease-in-out}
384
+ @keyframes lks-confirm-enter{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}
385
+ /* A7 toast enter/exit at upstream values (replaces the base sheet's simplified pair) */
386
+ @keyframes lks-toast-in-up{from{opacity:0;transform:translateY(-12px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}
387
+ @keyframes lks-toast-out-up{from{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(-8px) scale(.97)}}
388
+ .lks-ui .lks-toast{animation:lks-toast-in-up 220ms var(--ease-out-back)}
389
+ .lks-ui .lks-toast.exiting{animation:lks-toast-out-up 180ms var(--ease-out-quart) forwards}
390
+ .lks-ui .lks-toast-close{color:var(--ink-faint)}
391
+ .lks-ui .lks-toast-close:hover{color:var(--ink);background:rgb(var(--ink-rgb)/0.08)}
392
+ /* search rows: complete the half-covered pair */
393
+ .lks-ui .lks14-searchrow{border-bottom-color:var(--border)}
394
+ .lks-ui .lks14-searchrow-title{color:var(--ink-strong)}
395
+ .lks-ui .lks14-searchrow-course{color:var(--ink-faint)}
396
+
397
+ /* ══════════ B-track (P10b): the upstream rail frame + column skeletons ══════════ */
398
+ /* the floating topbar (absolute over the scrolling panes) */
399
+ .lks-ui .lks14-railtop{position:absolute;top:0;left:0;right:0;z-index:40;padding:8px;pointer-events:none}
400
+ .lks-ui .lks-railtabs,.lks-ui .lks14-railhead{pointer-events:auto}
401
+ .lks-ui .lks-railtabs{display:flex;padding:4px;border-radius:10px;gap:4px;margin-bottom:8px;
402
+ background:rgb(var(--surface-rail-rgb)/0.55);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}
403
+ .lks-ui .lks-railtab{flex:1;display:flex;align-items:center;justify-content:center;padding:6px 0;border-radius:8px;
404
+ font-size:12.5px;font-weight:700;color:rgb(255 255 255/0.5)}
405
+ .lks-ui .lks-railtab.on{background:rgb(var(--brand-rgb)/0.2);color:var(--brand)}
406
+ /* the glass title card: title row / mastery row / entry pills row */
407
+ .lks-ui .lks14-railhead{display:flex;flex-direction:column;gap:6px}
408
+ .lks-ui .lks14-railcard-row{display:flex;align-items:center;gap:8px}
409
+ .lks-ui .lks14-railpct{flex:none;font-size:12px;font-weight:800;color:#fff;font-variant-numeric:tabular-nums;text-shadow:0 1px 3px rgba(0,0,0,0.6)}
410
+ .lks-ui .lks14-masterybar{flex:1}
411
+ .lks-ui .lks-railpill{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:999px;
412
+ background:rgb(255 255 255/0.05);box-shadow:inset 0 0 0 1px rgb(255 255 255/0.1);color:rgb(var(--ink-rgb)/0.6);font-size:11px;font-weight:700}
413
+ .lks-ui .lks-railpill:hover{background:rgb(255 255 255/0.1)}
414
+ .lks-ui .lks-railpill.due{background:rgb(var(--review-rgb)/0.2);box-shadow:inset 0 0 0 1px rgb(var(--review-rgb)/0.3);color:var(--review)}
415
+ .lks-ui .lks-railpill-n{font-weight:800;font-variant-numeric:tabular-nums}
416
+ /* the sliding two-pane body */
417
+ .lks-ui .lks14-railbody{position:absolute;inset:0;overflow:hidden;z-index:10}
418
+ .lks-ui .lks14-railtrack{display:flex;height:100%;width:200%;transition:transform .3s var(--ease-out-expo)}
419
+ .lks-ui .lks14-railpane{width:50%;height:100%;position:relative}
420
+ .lks-ui .lks14-railscroll{height:100%;overflow-y:auto;overflow-x:hidden;padding:196px 8px 16px}
421
+ .lks-ui .lks-mapsec-list{display:flex;flex-direction:column;gap:24px}
422
+ .lks-ui .lks-mapsec{padding:12px 8px 0}
423
+ .lks-ui .lks14-railpane-import{height:100%;overflow-y:auto;padding:64px 12px 12px}
424
+ .lks-ui .lks14-raillist{display:flex;flex-direction:column;gap:8px;margin-bottom:10px}
425
+ .lks-ui .lks14-railcourse{text-align:left;padding:10px 12px;border-radius:12px;background:rgb(255 255 255/0.05);display:flex;flex-direction:column;gap:2px}
426
+ .lks-ui .lks14-railcourse:hover{background:rgb(255 255 255/0.1)}
427
+ .lks-ui .lks14-railcourse.on{background:rgb(var(--brand-rgb)/0.12);box-shadow:inset 0 0 0 1px rgb(var(--brand-rgb)/0.4)}
428
+ .lks-ui .lks14-railcourse-title{font-size:13px;font-weight:700;color:var(--ink-strong);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
429
+ .lks-ui .lks14-railcourse.on .lks14-railcourse-title{color:var(--brand)}
430
+ .lks-ui .lks14-railcourse-sub{font-size:10.5px;color:rgb(var(--ink-rgb)/0.4)}
431
+ .lks-ui .lks14-raildemo{margin-bottom:10px}
432
+ /* the full-rail overlays (search / review) */
433
+ .lks-ui .lks-railoverlay{position:absolute;inset:0;z-index:50;display:flex;flex-direction:column;gap:8px;padding:12px;overflow-y:auto;
434
+ background:rgb(var(--surface-rail-rgb)/0.92);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)}
435
+ .lks-ui .lks-railoverlay-head{display:flex;gap:8px;align-items:center}
436
+ .lks-ui .lks-railoverlay-head .lks14-search{flex:1}
437
+ /* chat: thin current-lesson row (ThreadSwitcher empty-state voice) */
438
+ .lks-ui .lks14-lessonrow{flex:none;padding:6px 14px;font-size:12px;font-weight:700;color:var(--ink-muted);opacity:.75}
439
+ /* composer: the soul pills ride the capsule's first row */
440
+ .lks-ui .lks14-soulrow{display:flex;align-items:center;gap:6px;padding:0 2px 6px}
441
+ .lks-ui .lks14-soullabel{flex:none;font-size:11px;color:var(--ink-faint)}
442
+ /* B6: assistant text is full-width prose (no card); user bubbles right-align */
443
+ .lks-ui .lks14-msg-assistant{background:none;border:none;box-shadow:none;border-radius:0;padding:0;max-width:80ch;font-size:.9375rem}
444
+ .lks-ui .lks14-msg-assistant.streaming{border:none;opacity:.85}
445
+ .lks-ui .lks14-msg-user{align-self:flex-end;max-width:85%}
446
+ /* B8: the empty state is a centered card with a 3D CTA */
447
+ .lks-ui .lks14-emptycard{margin:auto;display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;padding:24px;max-width:320px}
448
+ .lks-ui .lks14-emptycard-title{font-size:1rem;font-weight:800;color:var(--ink-strong)}
449
+ .lks-ui .lks14-emptycard-hint{font-size:.825rem;color:var(--ink-muted);line-height:1.6}
450
+ /* B5: notebook tab capsule + 960px reading column */
451
+ .lks-ui .lks14-notebody{margin:0 auto;max-width:960px;width:100%;padding:12px 20px 24px;box-sizing:border-box}
452
+ .lks-ui .lks14-viewtabs{background:rgb(var(--ink-rgb)/0.08);border-radius:999px;padding:3px;align-self:flex-start}
453
+ .lks-ui .lks14-readbar{position:sticky;top:0;z-index:20;background:var(--surface-2);border-radius:10px}
304
454
  `;
305
455
  //#endregion
306
456
  //#region src/client/styles.ts
@@ -362,10 +512,9 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
362
512
  .lks14-righthalf{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}
363
513
  .lks14-row{flex:1;min-height:0;display:flex}
364
514
  .lks14-col{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}
365
- .lks14-colhead{flex:none;display:flex;align-items:center;gap:8px;padding:10px 12px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1)}
366
515
 
367
516
  /* 左 rail: course picker, tree, review, import */
368
- .lks14-rail{flex:0 0 240px;border-right:1px solid var(--dsw-alias-border-l1);padding:0 10px 10px;overflow-y:auto}
517
+ .lks14-rail{flex:0 0 300px;border-right:none;padding:0;overflow:hidden;position:relative}
369
518
  .lks14-railhead{display:flex;align-items:center;gap:6px;margin-top:10px}
370
519
  .lks14-railtitle{font-weight:600;font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
371
520
  .lks14-railsub{font-size:12px;color:var(--dsw-alias-label-secondary);margin:6px 0}
@@ -383,13 +532,13 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
383
532
  .lks14-hint{font-size:11.5px;color:var(--dsw-alias-label-tertiary);margin-top:6px;line-height:1.6}
384
533
 
385
534
  /* 中 chat: the tutor stream + its own composer (upstream ChatStream/ChatComposer) */
386
- .lks14-chat{flex:1 1 46%;min-width:340px;border-right:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-0, transparent)}
535
+ .lks14-chat{flex:0 0 auto;width:clamp(480px,45%,800px);min-width:0;border-right:none}
387
536
  .lks14-chatlesson{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;color:var(--dsw-alias-label-tertiary)}
388
537
  .lks14-pills{display:inline-flex;gap:2px}
389
538
  .lks14-pill{border:none;background:none;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12px;padding:2px 8px;border-radius:7px;cursor:pointer}
390
539
  .lks14-pill:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
391
540
  .lks14-pill.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
392
- .lks14-stream{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}
541
+ .lks14-stream{flex:1;overflow-y:auto;padding:20px 20px 24px;display:flex;flex-direction:column;gap:24px}
393
542
  .lks14-msg{max-width:92%;line-height:1.65;font-size:13.5px}
394
543
  .lks14-msg-user{align-self:flex-end;background:var(--dsw-alias-bg-layer-2);border-radius:12px 12px 3px 12px;padding:7px 11px;white-space:pre-wrap}
395
544
  .lks14-msg-assistant{align-self:flex-start;background:var(--dsw-alias-bg-layer-1);border-radius:12px 12px 12px 3px;padding:8px 12px}
@@ -404,7 +553,7 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
404
553
  .lks14-opt:hover{background:var(--dsw-alias-bg-layer-2)}
405
554
  .lks14-opt:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
406
555
  .lks14-optletter{flex:none;font-weight:700;color:var(--dsw-alias-business-primary)}
407
- .lks14-starters{flex:none;display:flex;flex-wrap:wrap;gap:4px;padding:6px 12px}
556
+ .lks14-starters{flex:none;display:flex;flex-wrap:nowrap;gap:6px;padding:0 20px 6px;overflow-x:auto}
408
557
  .lks14-starter{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;border-radius:14px;padding:3px 10px;cursor:pointer}
409
558
  .lks14-starter:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}
410
559
  .lks14-starter:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
@@ -414,11 +563,11 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
414
563
  .lks14-composertext:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}
415
564
 
416
565
  /* 右 notebook: 讲解/概念图/笔记 (upstream NotebookPanel) */
417
- .lks14-note{flex:0 0 clamp(320px, 30vw, 460px);padding:0 12px 12px;overflow-y:auto}
566
+ .lks14-note{flex:1 1 auto;min-width:440px;padding:0;overflow-y:auto}
418
567
  .lks14-lessonhead{margin:10px 0 6px}
419
568
  .lks14-lessonhead h2{margin:0 0 4px;font-size:16px}
420
569
  .lks14-meta{font-size:12px;color:var(--dsw-alias-label-secondary);margin:2px 0}
421
- .lks14-viewtabs{display:flex;gap:2px;margin:8px 0}
570
+ .lks14-viewtabs{display:flex;gap:2px;margin:0 0 12px}
422
571
  .lks14-viewtab{border:none;background:none;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:12.5px;padding:4px 10px;border-radius:8px;cursor:pointer;display:inline-flex;align-items:center;gap:4px}
423
572
  .lks14-viewtab:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
424
573
  .lks14-viewtab.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
@@ -489,7 +638,7 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
489
638
  .lks14-switch{display:flex}
490
639
  .lks14-body{flex-direction:column}
491
640
  .lks14-rail{flex:none;max-height:32vh;border-right:none;border-bottom:1px solid var(--dsw-alias-border-l1)}
492
- .lks14-chat{min-width:0}
641
+ .lks14-chat{min-width:0;width:auto;flex:1 1 auto}
493
642
  .lks14-note{flex:1 1 auto;min-height:0}
494
643
  .lks14-body[data-pane='rail'] .lks14-righthalf{display:none}
495
644
  .lks14-body[data-pane='chat'] .lks14-rail,.lks14-body[data-pane='chat'] .lks14-note,
@@ -890,6 +1039,51 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
890
1039
  fill: "currentColor"
891
1040
  }));
892
1041
  }
1042
+ /** IconArrowUpFill16 — the 3D send button glyph (upstream: lucide Send direction) */
1043
+ function IconArrowUpFill16({ size = 16, className }) {
1044
+ return (0, react.createElement)("svg", {
1045
+ width: size,
1046
+ height: size,
1047
+ className,
1048
+ viewBox: "0 0 16 16",
1049
+ fill: "none",
1050
+ xmlns: "http://www.w3.org/2000/svg"
1051
+ }, (0, react.createElement)("path", {
1052
+ d: "M8 2.6 13.2 8h-3v5.4H5.8V8h-3L8 2.6Z",
1053
+ fill: "currentColor"
1054
+ }));
1055
+ }
1056
+ /** IconCloseFill16 — toast/modal close */
1057
+ function IconCloseFill16({ size = 16, className }) {
1058
+ return (0, react.createElement)("svg", {
1059
+ width: size,
1060
+ height: size,
1061
+ className,
1062
+ viewBox: "0 0 16 16",
1063
+ fill: "none",
1064
+ xmlns: "http://www.w3.org/2000/svg"
1065
+ }, (0, react.createElement)("path", {
1066
+ d: "M3.4 2 2 3.4 6.6 8 2 12.6 3.4 14 8 9.4l4.6 4.6 1.4-1.4L9.4 8 14 3.4 12.6 2 8 6.6 3.4 2Z",
1067
+ fill: "currentColor"
1068
+ }));
1069
+ }
1070
+ /** IconMaximizeOutline16 — artifact expand (upstream: lucide Maximize2 corners) */
1071
+ function IconMaximizeOutline16({ size = 16, className }) {
1072
+ return (0, react.createElement)("svg", {
1073
+ width: size,
1074
+ height: size,
1075
+ className,
1076
+ viewBox: "0 0 16 16",
1077
+ fill: "none",
1078
+ xmlns: "http://www.w3.org/2000/svg"
1079
+ }, (0, react.createElement)("path", {
1080
+ d: "M9.5 2H14v4.5M6.5 14H2V9.5M14 2 9.2 6.8M2 14l4.8-4.8",
1081
+ stroke: "currentColor",
1082
+ "stroke-width": 1.6,
1083
+ "stroke-linecap": "round",
1084
+ "stroke-linejoin": "round"
1085
+ }));
1086
+ }
893
1087
  //#endregion
894
1088
  //#region src/client/data.ts
895
1089
  /**
@@ -2001,6 +2195,15 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
2001
2195
  "map.node.locked": "锁定 — 先完成前面的课时",
2002
2196
  "map.node.due": "到了复习时间",
2003
2197
  "map.exam.locked": "考试未解锁 — 本章节全部课时掌握度 ≥50% 后开放",
2198
+ "map.tab.map": "地图",
2199
+ "map.tab.import": "导入",
2200
+ "map.search.label": "搜索",
2201
+ "map.review.label": "复习",
2202
+ "map.overlay.close": "关闭",
2203
+ "chat.start": "开始学习",
2204
+ "chat.start.hint": "从当前课时的第一个开场动作开始",
2205
+ "prompt.start": "你好,我们开始学这一课吧:先给我一个两三句话的导览,再从第一个知识点讲起。",
2206
+ "mode.label": "风格:",
2004
2207
  "companion.poke": "戳一戳伴学伙伴",
2005
2208
  "companion.poked": "伴学伙伴向你眨了眨眼",
2006
2209
  "companion.form.ember": "小焰",
@@ -2028,6 +2231,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
2028
2231
  "rail.due": "待复习 {count}",
2029
2232
  "rail.due.over": "超{days}天",
2030
2233
  "rail.due.start": "开始复习",
2234
+ "rail.due.none": "暂时没有到期的复习,保持节奏!",
2031
2235
  "rail.due.tag": "这课时的复习今天到期(SM-2)",
2032
2236
  "rail.delete": "删除本课程",
2033
2237
  "rail.delete.confirm": "确认删除?",
@@ -2204,6 +2408,15 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
2204
2408
  "map.node.locked": "Locked — finish the previous lessons first",
2205
2409
  "map.node.due": "Due for review",
2206
2410
  "map.exam.locked": "Exam locked — every lesson in this section needs ≥50% mastery",
2411
+ "map.tab.map": "Map",
2412
+ "map.tab.import": "Import",
2413
+ "map.search.label": "Search",
2414
+ "map.review.label": "Review",
2415
+ "map.overlay.close": "Close",
2416
+ "chat.start": "Start learning",
2417
+ "chat.start.hint": "Begin with this lesson’s first starter move",
2418
+ "prompt.start": "Hi — let’s start this lesson: give me a two-sentence tour, then begin with the first concept.",
2419
+ "mode.label": "Style: ",
2207
2420
  "companion.poke": "Poke the companion",
2208
2421
  "companion.poked": "The companion winks at you",
2209
2422
  "companion.form.ember": "Ember",
@@ -2231,6 +2444,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
2231
2444
  "rail.due": "{count} due",
2232
2445
  "rail.due.over": "{days}d overdue",
2233
2446
  "rail.due.start": "Start reviewing",
2447
+ "rail.due.none": "Nothing due right now — keep the streak!",
2234
2448
  "rail.due.tag": "this lesson's review is due today (SM-2)",
2235
2449
  "rail.delete": "Delete this course",
2236
2450
  "rail.delete.confirm": "Delete?",
@@ -3375,7 +3589,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
3375
3589
  onClick: () => {
3376
3590
  setExpanded(true);
3377
3591
  }
3378
- }, "⤢")), body, expanded ? (0, react.createElement)("div", {
3592
+ }, (0, react.createElement)(IconMaximizeOutline16, { size: 13 }))), body, expanded ? (0, react.createElement)("div", {
3379
3593
  className: "lks-acard-modal",
3380
3594
  onClick: () => {
3381
3595
  setExpanded(false);
@@ -3390,7 +3604,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
3390
3604
  onClick: () => {
3391
3605
  setExpanded(false);
3392
3606
  }
3393
- }, "×")), failed ? (0, react.createElement)("pre", { className: "lks-acard-code" }, mermaid) : (0, react.createElement)("div", { ref: (el) => {
3607
+ }, (0, react.createElement)(IconCloseFill16, { size: 13 }))), failed ? (0, react.createElement)("pre", { className: "lks-acard-code" }, mermaid) : (0, react.createElement)("div", { ref: (el) => {
3394
3608
  if (el !== null) renderMermaidInto(el, mermaid, () => {
3395
3609
  setFailed(true);
3396
3610
  });
@@ -4381,10 +4595,10 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4381
4595
  }), (0, react.createElement)("div", { className: "lks14-righthalf" }, (0, react.createElement)("div", { className: "lks14-appheader" }, (0, react.createElement)("span", { className: "lks-hdr-title" }, lesson?.courseTitle ?? tr("tab.label")), (0, react.createElement)("span", {
4382
4596
  className: "lks-hdr-xp",
4383
4597
  title: tr("header.xp", { xp: progress?.totalXp ?? 0 })
4384
- }, (0, react.createElement)("span", { className: "lks-hdr-stat" }, "⚡"), (0, react.createElement)("span", { className: "lks-hdr-xpbar" }, (0, react.createElement)("i", { style: { width: `${Math.min(100, progress?.levelPct ?? 0)}%` } }))), (0, react.createElement)("span", {
4598
+ }, (0, react.createElement)("span", { className: "lks-hdr-stat lks-hdr-glyph" }, (0, react.createElement)(IconBoltFill16, { size: 14 })), (0, react.createElement)("span", { className: "lks-hdr-xpbar" }, (0, react.createElement)("i", { style: { width: `${Math.min(100, progress?.levelPct ?? 0)}%` } }))), (0, react.createElement)("span", {
4385
4599
  className: "lks-hdr-stat lks-hdr-streak",
4386
4600
  title: tr("header.streak")
4387
- }, "🔥", String(progress?.streak ?? 0)), (0, react.createElement)("span", {
4601
+ }, (0, react.createElement)("span", { className: "lks-hdr-glyph" }, (0, react.createElement)(IconFlameFill16, { size: 14 })), String(progress?.streak ?? 0)), (0, react.createElement)("span", {
4388
4602
  className: "lks-hdr-stat",
4389
4603
  title: tr("header.level")
4390
4604
  }, `Lv${String(progress?.level ?? 1)}`)), (0, react.createElement)("div", { className: "lks14-row" }, (0, react.createElement)(ChatPane, {
@@ -4469,7 +4683,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4469
4683
  onClick: () => {
4470
4684
  toastStore.startExit(t.id);
4471
4685
  }
4472
- }, "×"))));
4686
+ }, (0, react.createElement)(IconCloseFill16, { size: 12 })))));
4473
4687
  }
4474
4688
  /** Module-level shell handle so the panel can suppress hand-back on internal opens. */
4475
4689
  const PANEL_SHELL = { current: null };
@@ -4489,8 +4703,10 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4489
4703
  }));
4490
4704
  };
4491
4705
  const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
4492
- const [showImport, setShowImport] = (0, react.useState)(false);
4493
4706
  const [error, setError] = (0, react.useState)(null);
4707
+ const [panel, setPanel] = (0, react.useState)("map");
4708
+ const [searchOpen, setSearchOpen] = (0, react.useState)(false);
4709
+ const [reviewOpen, setReviewOpen] = (0, react.useState)(false);
4494
4710
  const reportError = (err) => {
4495
4711
  setError(err instanceof Error ? err.message : String(err));
4496
4712
  };
@@ -4539,121 +4755,162 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4539
4755
  data,
4540
4756
  searchLessons
4541
4757
  ]);
4542
- const body = data === null ? (0, react.createElement)("div", { className: "lks14-empty" }, tr("loading")) : data.courses.length === 0 ? (0, react.createElement)("div", { className: "lks14-empty" }, tr("rail.empty.title"), (0, react.createElement)("br"), (0, react.createElement)("button", {
4543
- className: "lks-btn primary",
4544
- style: { margin: "10px 0" },
4758
+ const courseId = data !== null && data.courses.length > 0 ? data.courses.some((c) => c.courseId === selectedCourse) ? selectedCourse : data.courses[0].courseId : null;
4759
+ const course = data?.courses.find((c) => c.courseId === courseId) ?? null;
4760
+ const topbar = data === null ? null : (0, react.createElement)("div", { className: "lks14-railtop" }, (0, react.createElement)("div", { className: "lks-railtabs" }, ...["map", "import"].map((tab) => (0, react.createElement)("button", {
4761
+ key: tab,
4762
+ className: `lks-railtab${panel === tab ? " on" : ""}`,
4763
+ "aria-pressed": String(panel === tab),
4545
4764
  onClick: () => {
4546
- send(tr("prompt.import", { url: "https://github.com/microsoft/AI-For-Beginners" }));
4765
+ setPanel(tab);
4547
4766
  }
4548
- }, (0, react.createElement)(IconDownloadOutline16, null), tr("rail.empty.demo")), (0, react.createElement)(ImportRow, { send })) : (() => {
4549
- const courseId = data.courses.some((c) => c.courseId === selectedCourse) ? selectedCourse : data.courses[0].courseId;
4550
- const course = data.courses.find((c) => c.courseId === courseId);
4551
- return (0, react.createElement)("div", { className: `lks-map lks-sky-${pickSky(courseId)}` }, (0, react.createElement)("div", { className: "lks14-railhead" }, data.courses.length > 1 ? (0, react.createElement)("select", {
4552
- className: "lks-set-select",
4553
- value: courseId,
4554
- onChange: (e) => {
4555
- setSelectedCourse(e.target.value);
4556
- setConfirmDelete(false);
4557
- }
4558
- }, ...data.courses.map((c) => (0, react.createElement)("option", {
4559
- key: c.courseId,
4560
- value: c.courseId
4561
- }, c.title))) : (0, react.createElement)("div", {
4562
- className: "lks14-railtitle",
4563
- title: course.title
4564
- }, course.title), (0, react.createElement)("button", {
4565
- className: `lks-btn ${confirmDelete ? "primary" : "ghost"}`,
4566
- title: confirmDelete ? tr("rail.delete.title.confirm") : tr("rail.delete"),
4567
- onClick: (e) => {
4568
- e.stopPropagation();
4569
- if (!confirmDelete) {
4570
- setConfirmDelete(true);
4571
- return;
4572
- }
4573
- setConfirmDelete(false);
4574
- deleteCourse(courseId).then(() => {
4575
- setSelectedCourse("");
4576
- }, reportError);
4767
+ }, tr(tab === "map" ? "map.tab.map" : "map.tab.import")))), panel === "map" && course !== null ? (0, react.createElement)("div", { className: "lks14-railhead" }, (0, react.createElement)("div", { className: "lks14-railcard-row" }, data.courses.length > 1 ? (0, react.createElement)("select", {
4768
+ className: "lks-set-select",
4769
+ value: courseId ?? "",
4770
+ onChange: (e) => {
4771
+ setSelectedCourse(e.target.value);
4772
+ setConfirmDelete(false);
4773
+ }
4774
+ }, ...data.courses.map((c) => (0, react.createElement)("option", {
4775
+ key: c.courseId,
4776
+ value: c.courseId
4777
+ }, c.title))) : (0, react.createElement)("div", {
4778
+ className: "lks14-railtitle",
4779
+ title: course.title
4780
+ }, course.title), (0, react.createElement)("button", {
4781
+ className: `lks-btn ${confirmDelete ? "primary" : "ghost"}`,
4782
+ title: confirmDelete ? tr("rail.delete.title.confirm") : tr("rail.delete"),
4783
+ onClick: (e) => {
4784
+ e.stopPropagation();
4785
+ if (!confirmDelete) {
4786
+ setConfirmDelete(true);
4787
+ return;
4577
4788
  }
4578
- }, confirmDelete ? tr("rail.delete.confirm") : (0, react.createElement)(IconTrashOutline16, { size: 14 }))), (0, react.createElement)("div", { className: "lks14-railsub" }, tr("rail.mastered", {
4579
- mastered: course.mastered,
4580
- total: course.total
4581
- })), (0, react.createElement)("div", {
4582
- className: `lks14-masterybar${course.avgMasteryPct === 100 ? " gold" : ""}`,
4583
- title: course.avgMasteryPct === null ? tr("rail.avg.none") : tr("rail.avg", { pct: course.avgMasteryPct })
4584
- }, (0, react.createElement)("i", { style: { transform: `scaleX(${(course.avgMasteryPct ?? 0) / 100})` } })), (0, react.createElement)("input", {
4585
- className: "lks14-search",
4586
- type: "search",
4587
- placeholder: tr("rail.search"),
4588
- value: query,
4589
- onChange: (e) => {
4590
- setQuery(e.target.value);
4789
+ setConfirmDelete(false);
4790
+ deleteCourse(courseId ?? "").then(() => {
4791
+ setSelectedCourse("");
4792
+ }, reportError);
4793
+ }
4794
+ }, confirmDelete ? tr("rail.delete.confirm") : (0, react.createElement)(IconTrashOutline16, { size: 14 }))), (0, react.createElement)("div", { className: "lks14-railcard-row" }, (0, react.createElement)("div", {
4795
+ className: `lks14-masterybar${course.avgMasteryPct === 100 ? " gold" : ""}`,
4796
+ title: course.avgMasteryPct === null ? tr("rail.avg.none") : tr("rail.avg", { pct: course.avgMasteryPct })
4797
+ }, (0, react.createElement)("i", { style: { transform: `scaleX(${(course.avgMasteryPct ?? 0) / 100})` } })), (0, react.createElement)("span", { className: "lks14-railpct" }, `${String(course.avgMasteryPct ?? 0)}%`)), (0, react.createElement)("div", { className: "lks14-railcard-row" }, (0, react.createElement)("button", {
4798
+ className: "lks-railpill",
4799
+ title: tr("rail.search"),
4800
+ onClick: () => {
4801
+ setSearchOpen(true);
4802
+ }
4803
+ }, (0, react.createElement)(IconGlobeOutline14, { size: 13 }), tr("map.search.label")), (0, react.createElement)("button", {
4804
+ className: `lks-railpill${data.dueCount > 0 ? " due" : ""}`,
4805
+ title: data.dueCount > 0 ? tr("rail.due", { count: data.dueCount }) : tr("map.review.label"),
4806
+ onClick: () => {
4807
+ setReviewOpen(true);
4808
+ }
4809
+ }, (0, react.createElement)(IconBookFill16, { size: 13 }), tr("map.review.label"), data.dueCount > 0 ? (0, react.createElement)("span", { className: "lks-railpill-n" }, String(data.dueCount)) : null))) : null);
4810
+ const mapPane = data === null ? (0, react.createElement)("div", { className: "lks14-empty" }, tr("loading")) : course === null ? null : (0, react.createElement)("div", { className: "lks14-railscroll" }, (0, react.createElement)("div", { className: "lks-mapsec-list" }, ...course.sections.flatMap((section) => {
4811
+ const examAllowed = examOpen(section.lessons);
4812
+ const lessons = section.lessons.filter((l) => query.trim() === "" || titleMatches(l.title, query) || l.focus);
4813
+ if (lessons.length === 0) return [];
4814
+ const open = query.trim() !== "" || effectiveOpen(section.title, sectionDefaultOpen(section), sectionOverrides);
4815
+ return [(0, react.createElement)(MapSectionView, {
4816
+ key: section.title,
4817
+ section: {
4818
+ title: section.title,
4819
+ index: section.index,
4820
+ lessons
4591
4821
  },
4592
- onKeyDown: (e) => {
4593
- if (e.key === "Escape") {
4594
- setQuery("");
4595
- setSearchRows([]);
4596
- }
4597
- if (e.key !== "Enter" || query.trim() === "") return;
4598
- const first = searchRows[0];
4599
- if (first !== void 0) {
4600
- setQuery("");
4601
- setSearchRows([]);
4602
- setFocus(first.lessonId).catch(reportError);
4603
- }
4822
+ examAllowed,
4823
+ open,
4824
+ onToggle: () => {
4825
+ toggleSection(section.title, !open);
4826
+ },
4827
+ onJump: (id) => {
4828
+ setFocus(id).catch(reportError);
4604
4829
  }
4605
- }), searchRows.length > 0 ? (0, react.createElement)("div", { className: "lks14-searchpanel" }, ...searchRows.map((row) => (0, react.createElement)("button", {
4606
- key: row.lessonId,
4607
- className: "lks14-searchrow",
4608
- title: tr("rail.search.jump"),
4609
- onClick: () => {
4830
+ })];
4831
+ })));
4832
+ const importPane = data === null ? null : (0, react.createElement)("div", { className: "lks14-railpane-import" }, data.courses.length > 1 ? (0, react.createElement)("div", { className: "lks14-raillist" }, ...data.courses.map((c) => (0, react.createElement)("button", {
4833
+ key: c.courseId,
4834
+ className: `lks14-railcourse${c.courseId === courseId ? " on" : ""}`,
4835
+ onClick: () => {
4836
+ setSelectedCourse(c.courseId);
4837
+ setPanel("map");
4838
+ }
4839
+ }, (0, react.createElement)("span", { className: "lks14-railcourse-title" }, c.title), (0, react.createElement)("span", { className: "lks14-railcourse-sub" }, tr("rail.mastered", {
4840
+ mastered: c.mastered,
4841
+ total: c.total
4842
+ }))))) : null, data.courses.length === 0 ? (0, react.createElement)("div", { className: "lks14-empty" }, tr("rail.empty.title")) : null, (0, react.createElement)("button", {
4843
+ className: "lks-btn ghost lks14-raildemo",
4844
+ onClick: () => {
4845
+ send(tr("prompt.import", { url: "https://github.com/microsoft/AI-For-Beginners" }));
4846
+ }
4847
+ }, (0, react.createElement)(IconDownloadOutline16, null), tr("rail.empty.demo")), (0, react.createElement)(ImportRow, { send }));
4848
+ const searchOverlay = searchOpen && data !== null ? (0, react.createElement)("div", { className: "lks-railoverlay" }, (0, react.createElement)("div", { className: "lks-railoverlay-head" }, (0, react.createElement)("input", {
4849
+ className: "lks14-search",
4850
+ type: "search",
4851
+ placeholder: tr("rail.search"),
4852
+ value: query,
4853
+ autoFocus: true,
4854
+ onChange: (e) => {
4855
+ setQuery(e.target.value);
4856
+ },
4857
+ onKeyDown: (e) => {
4858
+ if (e.key === "Escape") {
4610
4859
  setQuery("");
4611
4860
  setSearchRows([]);
4612
- setFocus(row.lessonId).catch(reportError);
4613
- }
4614
- }, (0, react.createElement)("span", { className: "lks14-searchrow-title" }, row.title), row.courseTitle !== "" ? (0, react.createElement)("span", { className: "lks14-searchrow-course" }, row.courseTitle) : null, row.snippet !== "" ? (0, react.createElement)("span", { className: "lks14-searchrow-snip" }, row.snippet) : null))) : null, data.dueCount > 0 ? (0, react.createElement)("div", { className: "lks14-duebox" }, (0, react.createElement)(IconRefreshOutline16, { size: 13 }), tr("rail.due", { count: data.dueCount }), ...data.due.map((d) => (0, react.createElement)("button", {
4615
- key: d.lessonId,
4616
- className: "lks14-dueitem",
4617
- title: tr("review.jump"),
4618
- onClick: () => {
4619
- setFocus(d.lessonId).catch(reportError);
4861
+ setSearchOpen(false);
4620
4862
  }
4621
- }, (0, react.createElement)("span", null, d.lessonTitle), d.overdueDays > 0 ? (0, react.createElement)("span", { className: "lks14-over" }, tr("rail.due.over", { days: d.overdueDays })) : null)), (0, react.createElement)("button", {
4622
- className: "lks-btn ghost",
4623
- style: { marginTop: "6px" },
4624
- onClick: () => {
4625
- send(tr("prompt.review"));
4626
- }
4627
- }, tr("rail.due.start"))) : null, ...course.sections.flatMap((section) => {
4628
- const examAllowed = examOpen(section.lessons);
4629
- const lessons = section.lessons.filter((l) => query.trim() === "" || titleMatches(l.title, query) || l.focus);
4630
- if (lessons.length === 0) return [];
4631
- const open = query.trim() !== "" || effectiveOpen(section.title, sectionDefaultOpen(section), sectionOverrides);
4632
- return [(0, react.createElement)(MapSectionView, {
4633
- key: section.title,
4634
- section: {
4635
- title: section.title,
4636
- index: section.index,
4637
- lessons
4638
- },
4639
- examAllowed,
4640
- open,
4641
- onToggle: () => {
4642
- toggleSection(section.title, !open);
4643
- },
4644
- onJump: (id) => {
4645
- setFocus(id).catch(reportError);
4646
- }
4647
- })];
4648
- }), (0, react.createElement)("button", {
4649
- className: "lks-btn ghost",
4650
- style: { marginTop: "10px" },
4651
- onClick: () => {
4652
- setShowImport(!showImport);
4863
+ if (e.key !== "Enter" || query.trim() === "") return;
4864
+ const first = searchRows[0];
4865
+ if (first !== void 0) {
4866
+ setQuery("");
4867
+ setSearchRows([]);
4868
+ setSearchOpen(false);
4869
+ setFocus(first.lessonId).catch(reportError);
4653
4870
  }
4654
- }, showImport ? tr("rail.import.close") : null, tr("rail.import.toggle")), showImport ? (0, react.createElement)(ImportRow, { send }) : null);
4655
- })();
4656
- return (0, react.createElement)("div", { className: "lks14-col lks14-rail" }, (0, react.createElement)("div", { className: "lks14-colhead" }, tr("col.rail")), body, (0, react.createElement)("div", { className: "lks-propcard-err" }, error));
4871
+ }
4872
+ }), (0, react.createElement)("button", {
4873
+ className: "lks-btn ghost",
4874
+ onClick: () => {
4875
+ setQuery("");
4876
+ setSearchRows([]);
4877
+ setSearchOpen(false);
4878
+ }
4879
+ }, tr("map.overlay.close"))), searchRows.length > 0 ? (0, react.createElement)("div", { className: "lks14-searchpanel" }, ...searchRows.map((row) => (0, react.createElement)("button", {
4880
+ key: row.lessonId,
4881
+ className: "lks14-searchrow",
4882
+ title: tr("rail.search.jump"),
4883
+ onClick: () => {
4884
+ setQuery("");
4885
+ setSearchRows([]);
4886
+ setSearchOpen(false);
4887
+ setFocus(row.lessonId).catch(reportError);
4888
+ }
4889
+ }, (0, react.createElement)("span", { className: "lks14-searchrow-title" }, row.title), row.courseTitle !== "" ? (0, react.createElement)("span", { className: "lks14-searchrow-course" }, row.courseTitle) : null, row.snippet !== "" ? (0, react.createElement)("span", { className: "lks14-searchrow-snip" }, row.snippet) : null))) : null) : null;
4890
+ const reviewOverlay = reviewOpen && data !== null ? (0, react.createElement)("div", { className: "lks-railoverlay" }, (0, react.createElement)("div", { className: "lks-railoverlay-head" }, (0, react.createElement)("span", { className: "lks14-railtitle" }, data.dueCount > 0 ? tr("rail.due", { count: data.dueCount }) : tr("map.review.label")), (0, react.createElement)("button", {
4891
+ className: "lks-btn ghost",
4892
+ onClick: () => {
4893
+ setReviewOpen(false);
4894
+ }
4895
+ }, tr("map.overlay.close"))), ...data.due.map((d) => (0, react.createElement)("button", {
4896
+ key: d.lessonId,
4897
+ className: "lks14-dueitem",
4898
+ title: tr("review.jump"),
4899
+ onClick: () => {
4900
+ setFocus(d.lessonId).catch(reportError);
4901
+ }
4902
+ }, (0, react.createElement)("span", null, d.lessonTitle), d.overdueDays > 0 ? (0, react.createElement)("span", { className: "lks14-over" }, tr("rail.due.over", { days: d.overdueDays })) : null)), data.dueCount > 0 ? (0, react.createElement)("button", {
4903
+ className: "lks-btn primary",
4904
+ onClick: () => {
4905
+ setReviewOpen(false);
4906
+ send(tr("prompt.review"));
4907
+ }
4908
+ }, tr("rail.due.start")) : (0, react.createElement)("div", { className: "lks14-empty" }, tr("rail.due.none"))) : null;
4909
+ const effectivePanel = data !== null && data.courses.length === 0 ? "import" : panel;
4910
+ return (0, react.createElement)("div", { className: `lks14-col lks14-rail${courseId !== null ? ` lks-sky-${pickSky(courseId)}` : ""}` }, topbar, (0, react.createElement)("div", { className: "lks14-railbody" }, (0, react.createElement)("div", {
4911
+ className: "lks14-railtrack",
4912
+ style: { transform: effectivePanel === "map" ? "translateX(0)" : "translateX(-50%)" }
4913
+ }, (0, react.createElement)("div", { className: "lks14-railpane" }, mapPane), (0, react.createElement)("div", { className: "lks14-railpane" }, importPane))), searchOverlay, reviewOverlay, (0, react.createElement)("div", { className: "lks-propcard-err" }, error));
4657
4914
  }
4658
4915
  function ImportRow({ send }) {
4659
4916
  const [url, setUrl] = (0, react.useState)("");
@@ -4719,20 +4976,17 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4719
4976
  onClick: () => {
4720
4977
  send(tr("proposal.decline.msg", { lesson: proposal.lessonTitle }));
4721
4978
  }
4722
- }, tr("proposal.decline"))) : null, (0, react.createElement)("div", { className: "lks14-colhead" }, tr("col.tutor"), lesson !== null ? (0, react.createElement)("span", { className: "lks14-chatlesson" }, lesson.title) : null, (0, react.createElement)("span", { className: "lks14-pills" }, ...MODES$1.map((mode) => (0, react.createElement)("button", {
4723
- key: mode.id,
4724
- className: `lks14-pill${data?.mode === mode.id ? " on" : ""}`,
4725
- "aria-pressed": String(data?.mode === mode.id),
4726
- "aria-disabled": dormant || void 0,
4727
- title: dormant ? tr("tutor.dormant") : tr(mode.hintKey),
4728
- onClick: () => {
4729
- if (!dormant) fire(setMode(mode.id));
4730
- }
4731
- }, tr(mode.labelKey))))), (0, react.createElement)("div", {
4979
+ }, tr("proposal.decline"))) : null, lesson !== null || !dormant ? (0, react.createElement)("div", { className: "lks14-lessonrow" }, lesson !== null ? (0, react.createElement)("span", null, lesson.title) : (0, react.createElement)("span", null, tr("col.tutor"))) : null, (0, react.createElement)("div", {
4732
4980
  className: "lks14-stream",
4733
4981
  ref: streamEl,
4734
4982
  "data-lks-feed": rows.length > 0 ? `rows:${String(rows.length)}` : bound ? feedAttached ? "attached-empty" : "waiting" : "no-thread"
4735
- }, rows.length === 0 ? (0, react.createElement)("div", { className: "lks14-empty" }, dormant ? tr("tutor.dormant") : tr("tutor.empty"), (0, react.createElement)("br"), tr("tutor.empty.hint")) : rows.map((row, i) => chatRow(row, !dormant && i === lastAssistant ? { send } : void 0)), rows.length > 0 && rows[rows.length - 1].role === "user" ? (0, react.createElement)("div", { className: "lks14-thinking" }, (0, react.createElement)("i", null), (0, react.createElement)("i", null), (0, react.createElement)("i", null)) : null), ...(lesson?.artifacts ?? []).filter((a) => a.artifactType === "quiz").map((a) => (0, react.createElement)(QuizCard, {
4983
+ }, rows.length === 0 ? (0, react.createElement)("div", { className: "lks14-emptycard" }, (0, react.createElement)("div", { className: "lks14-emptycard-title" }, dormant ? tr("tutor.dormant") : tr("tutor.empty")), (0, react.createElement)("div", { className: "lks14-emptycard-hint" }, tr("tutor.empty.hint")), !dormant ? (0, react.createElement)("button", {
4984
+ className: "lks-btn primary",
4985
+ title: tr("chat.start.hint"),
4986
+ onClick: () => {
4987
+ send(starters[0]?.message ?? tr("prompt.start"));
4988
+ }
4989
+ }, tr("chat.start")) : null) : rows.map((row, i) => chatRow(row, !dormant && i === lastAssistant ? { send } : void 0)), rows.length > 0 && rows[rows.length - 1].role === "user" ? (0, react.createElement)("div", { className: "lks14-thinking" }, (0, react.createElement)("i", null), (0, react.createElement)("i", null), (0, react.createElement)("i", null)) : null), ...(lesson?.artifacts ?? []).filter((a) => a.artifactType === "quiz").map((a) => (0, react.createElement)(QuizCard, {
4736
4990
  key: a.id,
4737
4991
  lessonId: lesson.lessonId,
4738
4992
  artifactId: a.id,
@@ -4746,7 +5000,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4746
5000
  key: a.id,
4747
5001
  artifact: a,
4748
5002
  send
4749
- })), (0, react.createElement)("div", { className: "lks14-starters" }, ...starters.map((s) => (0, react.createElement)("button", {
5003
+ })), rows.length > 0 ? (0, react.createElement)("div", { className: "lks14-starters" }, ...starters.map((s) => (0, react.createElement)("button", {
4750
5004
  key: s.label,
4751
5005
  className: "lks14-starter",
4752
5006
  "aria-disabled": dormant || void 0,
@@ -4754,7 +5008,16 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4754
5008
  onClick: () => {
4755
5009
  if (!dormant) send(s.message);
4756
5010
  }
4757
- }, s.label))), (0, react.createElement)("div", { className: "lks14-composer" }, (0, react.createElement)("div", { className: "lks14-composer-card" }, (0, react.createElement)("textarea", {
5011
+ }, s.label))) : null, (0, react.createElement)("div", { className: "lks14-composer" }, (0, react.createElement)("div", { className: "lks14-composer-card" }, (0, react.createElement)("div", { className: "lks14-soulrow" }, (0, react.createElement)("span", { className: "lks14-soullabel" }, tr("mode.label")), (0, react.createElement)("span", { className: "lks14-pills" }, ...MODES$1.map((mode) => (0, react.createElement)("button", {
5012
+ key: mode.id,
5013
+ className: `lks14-pill${data?.mode === mode.id ? " on" : ""}`,
5014
+ "aria-pressed": String(data?.mode === mode.id),
5015
+ "aria-disabled": dormant || void 0,
5016
+ title: dormant ? tr("tutor.dormant") : tr(mode.hintKey),
5017
+ onClick: () => {
5018
+ if (!dormant) fire(setMode(mode.id));
5019
+ }
5020
+ }, mode.id === "direct" ? (0, react.createElement)(IconBoltFill16, { size: 12 }) : mode.id === "guide" ? (0, react.createElement)(IconGoalOutline16, { size: 12 }) : (0, react.createElement)(IconBookFill16, { size: 12 }), tr(mode.labelKey))))), (0, react.createElement)("textarea", {
4758
5021
  className: "lks14-composertext",
4759
5022
  placeholder: busy ? tr("composer.busy") : tr("tutor.empty.hint"),
4760
5023
  value: draft,
@@ -4774,7 +5037,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
4774
5037
  }, busy ? (0, react.createElement)(IconLoadingOutline16, {
4775
5038
  size: 16,
4776
5039
  className: "lks-spin"
4777
- }) : "↑")), sendError !== null || error !== null ? (0, react.createElement)("div", { className: "lks-propcard-err" }, sendError ?? error) : null));
5040
+ }) : (0, react.createElement)(IconArrowUpFill16, { size: 18 }))), sendError !== null || error !== null ? (0, react.createElement)("div", { className: "lks-propcard-err" }, sendError ?? error) : null));
4778
5041
  }
4779
5042
  /** 右栏:the notebook — 讲解/概念图/笔记 tabs (upstream NotebookPanel arrangement). */
4780
5043
  function NotebookPane({ data, deleteNote, send, companionEvent }) {
@@ -5004,7 +5267,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
5004
5267
  setEnhanceTick(Date.now());
5005
5268
  });
5006
5269
  }, [tab, lesson?.html]);
5007
- const body = lesson === null ? (0, react.createElement)("div", { className: "lks14-empty" }, tr("bb.empty"), (0, react.createElement)("br"), tr("bb.empty.hint")) : (0, react.createElement)("div", { className: "lks14-notebody" }, (0, react.createElement)("div", { className: "lks14-lessonhead" }, (0, react.createElement)("h2", null, lesson.title), (0, react.createElement)("div", { className: "lks14-meta" }, `${lesson.courseTitle} · ${statusTitle("study", lesson.status)}` + (lesson.masteryPct === null ? "" : ` · ${tr("bb.mastery", { pct: lesson.masteryPct })}`))), (0, react.createElement)("div", { className: "lks14-viewtabs" }, (0, react.createElement)("button", {
5270
+ const body = lesson === null ? (0, react.createElement)("div", { className: "lks14-empty" }, tr("bb.empty"), (0, react.createElement)("br"), tr("bb.empty.hint")) : (0, react.createElement)("div", { className: "lks14-notebody" }, (0, react.createElement)("div", { className: "lks14-viewtabs" }, (0, react.createElement)("button", {
5008
5271
  className: `lks14-viewtab${tab === "teach" ? " on" : ""}`,
5009
5272
  "aria-pressed": String(tab === "teach"),
5010
5273
  onClick: () => {
@@ -5027,7 +5290,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
5027
5290
  setUnseenTick(Date.now());
5028
5291
  }
5029
5292
  }
5030
- }, tr("bb.notes"), unseen.length > 0 ? (0, react.createElement)("span", { className: "lks-viewtab-badge" }, String(unseen.length)) : null)), tab === "teach" ? (0, react.createElement)("div", null, lesson.due ? (0, react.createElement)("div", {
5293
+ }, tr("bb.notes"), unseen.length > 0 ? (0, react.createElement)("span", { className: "lks-viewtab-badge" }, String(unseen.length)) : null)), tab !== "notes" ? (0, react.createElement)("div", { className: "lks14-lessonhead" }, (0, react.createElement)("h2", null, lesson.title), (0, react.createElement)("div", { className: "lks14-meta" }, `${lesson.courseTitle} · ${statusTitle("study", lesson.status)}` + (lesson.masteryPct === null ? "" : ` · ${tr("bb.mastery", { pct: lesson.masteryPct })}`))) : null, tab === "teach" ? (0, react.createElement)("div", null, lesson.due ? (0, react.createElement)("div", {
5031
5294
  className: "lks-ratecard",
5032
5295
  "data-lks-rate": lesson.lessonId
5033
5296
  }, (0, react.createElement)("div", { className: "lks-ratecard-title" }, tr("review.rate.title")), (0, react.createElement)("div", { className: "lks-ratecard-opts" }, ...[
@@ -5145,7 +5408,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
5145
5408
  className: "lks-note-text",
5146
5409
  dangerouslySetInnerHTML: { __html: renderMarkdown(n.text) }
5147
5410
  }), n.quote !== null ? (0, react.createElement)("div", { className: "lks-note-q" }, `“${n.quote}”`) : null))))));
5148
- return (0, react.createElement)("div", { className: "lks14-col lks14-note" }, (0, react.createElement)("div", { className: "lks14-colhead" }, tr("col.bb")), body, (0, react.createElement)("div", { className: "lks-propcard-err" }, error));
5411
+ return (0, react.createElement)("div", { className: "lks14-col lks14-note" }, body, (0, react.createElement)("div", { className: "lks-propcard-err" }, error));
5149
5412
  }
5150
5413
  //#endregion
5151
5414
  //#region src/client/shell-entry.ts