dsh-plugin-lookatstudy 0.15.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/README.md +2 -2
- package/lib/client.js +967 -197
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +18 -5
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -18,8 +18,449 @@ window.__ModuleLoader__.load({
|
|
|
18
18
|
//#endregion
|
|
19
19
|
let react = require("react");
|
|
20
20
|
let react_dom_client = require("react-dom/client");
|
|
21
|
+
//#region src/client/upstream-theme.ts
|
|
22
|
+
/**
|
|
23
|
+
* The upstream LookatStudy skin (0.16.0 P9a) — the 1:1 visual port. Upstream
|
|
24
|
+
* is a Duolingo-style dark-first design system defined in its tailwind.config
|
|
25
|
+
* + index.css :root; this module carries those EXACT tokens (scoped to the
|
|
26
|
+
* panel root `.lks-ui`) and redefines the panel's class vocabulary to match
|
|
27
|
+
* upstream's components: the three-column surface ladder (rail darkest / chat
|
|
28
|
+
* surface-1 / notebook surface-2, no borders — depth by color step), the
|
|
29
|
+
* floating blur app-header, 3D push-down buttons, the claude.ai-style
|
|
30
|
+
* composer capsule, chat bubbles, typing dots, and the toast capsules.
|
|
31
|
+
* Host-chrome surfaces (dock pill, settings, toolviews) keep the dsw tokens —
|
|
32
|
+
* only the study panel wears the upstream skin.
|
|
33
|
+
* @module dsh-plugin-lookatstudy/client/upstream-theme
|
|
34
|
+
*/
|
|
35
|
+
const UPSTREAM_CSS = `
|
|
36
|
+
/* ── upstream design tokens (index.css :root, verbatim values) ── */
|
|
37
|
+
.lks-ui{
|
|
38
|
+
--brand:#58CC01;--brand-rgb:88 204 2;--brand-dark:#46A302;--brand-light:#7ED957;
|
|
39
|
+
--brand-glow:#58CC0159;--brand-ring:#58CC0126;
|
|
40
|
+
--accent:#1DB0F6;--accent-rgb:28 176 246;--accent-dark:#098CDC;--accent-light:#65C6FF;
|
|
41
|
+
--accent-glow:#1DB0F64C;--accent-ring:#1DB0F626;
|
|
42
|
+
--gold:#FFC801;--gold-rgb:255 200 0;--gold-dark:#D59800;--gold-light:#FFE294;
|
|
43
|
+
--gold-glow:#FFC80173;--gold-ring:#FFC80133;
|
|
44
|
+
--warning:#FF4B4B;--warning-rgb:255 75 75;--warning-dark:#D01C29;--warning-light:#FF7E77;
|
|
45
|
+
--warning-glow:#FF4B4B4C;--warning-ring:#FF4B4B26;--warning-tint:#FF4B4B1A;--warning-tint-border:#FF4B4B40;
|
|
46
|
+
--review:#FA6E1D;--review-rgb:255 122 26;--review-glow:#FA6E1D26;--review-tint:#FA6E1D1A;
|
|
47
|
+
--exam:#A855F7;--exam-rgb:168 85 247;--exam-dark:#7E22CE;--exam-light:#C084FC;--exam-locked:#524569;
|
|
48
|
+
--exam-glow:#A855F759;--exam-ring:#A855F726;
|
|
49
|
+
--surface-rail:#08090B;--surface-rail-rgb:8 9 11;
|
|
50
|
+
--surface-0:#0C0D0F;--surface-0-rgb:12 13 15;
|
|
51
|
+
--surface-1:#111114;--surface-1-rgb:17 17 20;
|
|
52
|
+
--surface-2:#1A1A1D;--surface-2-rgb:26 26 29;
|
|
53
|
+
--surface-3:#2A2B2E;--surface-3-rgb:42 43 46;
|
|
54
|
+
--ink:#F1F2F4;--ink-rgb:245 245 250;
|
|
55
|
+
--ink-strong:#FAFAFA;--ink-strong-rgb:250 250 250;
|
|
56
|
+
--ink-muted:#9D9EA2;--ink-muted-rgb:166 166 176;
|
|
57
|
+
--ink-faint:#88898C;--ink-faint-rgb:136 137 140;
|
|
58
|
+
--border:#252629;--border-rgb:37 38 41;--border-faint:#1A1A1D;--border-faint-rgb:26 26 29;
|
|
59
|
+
--shadow-rgb:0 0 0;--inner-highlight:rgb(255 255 255/0.55);
|
|
60
|
+
--ease-out-expo:cubic-bezier(0.16,1,0.3,1);
|
|
61
|
+
--ease-out-back:cubic-bezier(0.34,1.2,0.64,1);
|
|
62
|
+
--ease-spring:cubic-bezier(0.34,1.56,0.64,1);
|
|
63
|
+
--ease-out-quart:cubic-bezier(0.25,1,0.5,1);
|
|
64
|
+
font-family:'DIN Round',system-ui,-apple-system,sans-serif;
|
|
65
|
+
background:var(--surface-1);color:var(--ink);
|
|
66
|
+
font-feature-settings:'cv02','cv03','cv04','cv11';
|
|
67
|
+
-webkit-font-smoothing:antialiased;
|
|
68
|
+
}
|
|
69
|
+
/* the six-step type scale (rem, follows the host base) */
|
|
70
|
+
.lks-ui .lks-t-caption{font-size:.75rem}.lks-ui .lks-t-label{font-size:.825rem}
|
|
71
|
+
.lks-ui .lks-t-body{font-size:.875rem}.lks-ui .lks-t-lead{font-size:1rem}
|
|
72
|
+
.lks-ui .lks-t-title{font-size:1.125rem}.lks-ui .lks-t-hero{font-size:1.5rem}
|
|
73
|
+
|
|
74
|
+
/* ── the column ladder: no borders, depth by surface step (v0.6) ── */
|
|
75
|
+
.lks-ui .lks14-rail{background:var(--surface-rail);border-right:none}
|
|
76
|
+
.lks-ui .lks14-chat{background:var(--surface-1);border-right:none}
|
|
77
|
+
.lks-ui .lks14-note{background:var(--surface-2)}
|
|
78
|
+
.lks-ui .lks14-colhead{color:var(--ink-muted);border-bottom:1px solid var(--border-faint);letter-spacing:.02em;text-transform:none;font-size:.825rem}
|
|
79
|
+
|
|
80
|
+
/* ── the floating app-header over the right half (blur + gradient fade) ── */
|
|
81
|
+
.lks-ui .lks14-appheader{position:relative;z-index:30;flex:none;display:flex;align-items:center;height:46px;padding:0 14px;gap:10px}
|
|
82
|
+
.lks-ui .lks14-appheader::before{content:'';position:absolute;inset:0;z-index:-1;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
|
|
83
|
+
-webkit-mask:linear-gradient(to bottom,black 0%,black 65%,transparent 100%);mask:linear-gradient(to bottom,black 0%,black 65%,transparent 100%);
|
|
84
|
+
background:linear-gradient(to bottom,rgb(var(--surface-1-rgb)/0.85) 0%,rgb(var(--surface-1-rgb)/0.55) 60%,rgb(var(--surface-1-rgb)/0.15) 85%,transparent 100%)}
|
|
85
|
+
.lks-ui .lks-hdr-title{flex:1;min-width:0;font-size:.825rem;font-weight:700;color:var(--ink-strong);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
86
|
+
.lks-ui .lks-hdr-xp{display:flex;align-items:center;gap:5px;flex:none}
|
|
87
|
+
.lks-ui .lks-hdr-xpbar{width:92px;height:10px;border-radius:999px;background:rgb(var(--ink-rgb)/0.08);overflow:hidden;box-shadow:inset 0 2px 3px rgb(var(--shadow-rgb)/0.3)}
|
|
88
|
+
.lks-ui .lks-hdr-xpbar i{display:block;height:100%;background:linear-gradient(90deg,var(--gold),var(--gold-light));border-radius:999px;transition:width .5s var(--ease-out-expo)}
|
|
89
|
+
.lks-ui .lks-hdr-stat{display:inline-flex;align-items:center;gap:4px;font-size:.825rem;font-weight:700;color:var(--ink);flex:none}
|
|
90
|
+
.lks-ui .lks-hdr-streak{color:var(--review)}
|
|
91
|
+
|
|
92
|
+
/* ── 3D push-down buttons (the signature style) ── */
|
|
93
|
+
.lks-ui .lks-btn{border-radius:1rem;font-weight:700;transition:all .15s var(--ease-spring)}
|
|
94
|
+
.lks-ui .lks-btn.primary{background:var(--brand);color:#fff;box-shadow:0 4px 0 0 var(--brand-dark)}
|
|
95
|
+
.lks-ui .lks-btn.primary:hover{background:var(--brand-light)}
|
|
96
|
+
.lks-ui .lks-btn.primary:active{transform:translateY(2px);box-shadow:0 1px 0 0 var(--brand-dark)}
|
|
97
|
+
.lks-ui .lks-btn.primary:disabled{background:rgb(var(--ink-rgb)/0.15);color:var(--ink-faint);box-shadow:none;transform:none;cursor:not-allowed}
|
|
98
|
+
.lks-ui .lks-btn.ghost{background:rgb(var(--ink-rgb)/0.06);color:var(--ink);border:none;box-shadow:0 3px 0 0 rgb(var(--shadow-rgb)/0.2)}
|
|
99
|
+
.lks-ui .lks-btn.ghost:hover{color:var(--ink-strong);background:rgb(var(--ink-rgb)/0.1)}
|
|
100
|
+
.lks-ui .lks-btn.ghost:active{transform:translateY(2px);box-shadow:0 1px 0 0 rgb(var(--shadow-rgb)/0.2)}
|
|
101
|
+
|
|
102
|
+
/* ── chat stream: bubbles + typing dots ── */
|
|
103
|
+
.lks-ui .lks14-msg-user{background:rgb(var(--ink-rgb)/0.04);border-radius:1rem 1rem .375rem 1rem;padding:.625rem 1rem;color:var(--ink-strong);animation:lks-msg-enter .2s var(--ease-out-expo)}
|
|
104
|
+
.lks-ui .lks14-msg-assistant{background:var(--surface-2);border:1px solid var(--border-faint);border-radius:1rem;padding:1rem;box-shadow:0 1px 3px -1px rgb(var(--shadow-rgb)/0.08);animation:lks-msg-enter .2s var(--ease-out-expo);font-size:.875rem;line-height:1.65}
|
|
105
|
+
.lks-ui .lks14-msg.streaming{opacity:1;border-style:dashed}
|
|
106
|
+
@keyframes lks-msg-enter{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
107
|
+
.lks-ui .lks14-thinking{gap:5px}
|
|
108
|
+
@keyframes lks-typing-dot{0%,60%,100%{opacity:.3;transform:translateY(0)}30%{opacity:1;transform:translateY(-4px)}}
|
|
109
|
+
.lks-ui .lks14-thinking i{width:6px;height:6px;border-radius:999px;background:var(--brand);display:inline-block;animation:lks-typing-dot 1.2s ease-in-out infinite}
|
|
110
|
+
.lks-ui .lks14-thinking i:nth-child(2){animation-delay:.15s}
|
|
111
|
+
.lks-ui .lks14-thinking i:nth-child(3){animation-delay:.3s}
|
|
112
|
+
/* quiz options wear the neutral pill + brand hover */
|
|
113
|
+
.lks-ui .lks-qcard-opt{background:rgb(var(--ink-rgb)/0.05);border:2px solid transparent;border-radius:.75rem;color:var(--ink)}
|
|
114
|
+
.lks-ui .lks-qcard-opt:not(:disabled):hover{border-color:var(--brand)}
|
|
115
|
+
.lks-ui .lks-qcard-opt.right{border-color:var(--brand);color:var(--brand-light);background:rgb(var(--brand-rgb)/0.08)}
|
|
116
|
+
.lks-ui .lks-qcard-opt.wrong{border-color:var(--warning);color:var(--warning-light);background:var(--warning-tint)}
|
|
117
|
+
.lks-ui .lks-qcard-next{background:var(--brand);color:#fff;box-shadow:0 3px 0 0 var(--brand-dark);border-radius:1rem}
|
|
118
|
+
.lks-ui .lks-qcard-next:active{transform:translateY(2px);box-shadow:0 1px 0 0 var(--brand-dark)}
|
|
119
|
+
.lks-ui .lks-qcard-action{background:rgb(var(--ink-rgb)/0.06);border:none;color:var(--ink)}
|
|
120
|
+
.lks-ui .lks-qcard-action:hover{color:var(--ink-strong);background:rgb(var(--ink-rgb)/0.1)}
|
|
121
|
+
|
|
122
|
+
/* ── the composer capsule (claude.ai style) + circular 3D send ── */
|
|
123
|
+
.lks-ui .lks14-composer{border-top:none;background:linear-gradient(to bottom,transparent 0%,var(--surface-1) 40%)}
|
|
124
|
+
.lks-ui .lks14-composer-card{flex:1;display:flex;align-items:flex-end;gap:8px;background:rgb(var(--ink-rgb)/0.05);border-radius:1rem;padding:.625rem .75rem .5rem .875rem;transition:background .15s}
|
|
125
|
+
.lks-ui .lks14-composer-card:focus-within{background:rgb(var(--ink-rgb)/0.07)}
|
|
126
|
+
.lks-ui .lks14-composertext{background:transparent;border:none;box-shadow:none;color:var(--ink-strong);font-size:.875rem}
|
|
127
|
+
.lks-ui .lks14-composertext:focus{border-color:transparent}
|
|
128
|
+
.lks-ui .lks14-composertext::placeholder{color:var(--ink-faint)}
|
|
129
|
+
.lks-ui .lks-btn-send{width:36px;height:36px;border-radius:999px !important;padding:0 !important;display:inline-flex;align-items:center;justify-content:center;flex:none;background:var(--brand);color:#fff;box-shadow:0 3px 0 0 var(--brand-dark);border:none;cursor:pointer;transition:all .15s var(--ease-spring)}
|
|
130
|
+
.lks-ui .lks-btn-send:hover{background:var(--brand-light)}
|
|
131
|
+
.lks-ui .lks-btn-send:active{transform:translateY(2px);box-shadow:0 1px 0 0 var(--brand-dark)}
|
|
132
|
+
.lks-ui .lks-btn-send:disabled{background:rgb(var(--ink-rgb)/0.15);color:var(--ink-faint);box-shadow:none;transform:none;cursor:not-allowed}
|
|
133
|
+
|
|
134
|
+
/* ── toast capsules on the upstream tokens ── */
|
|
135
|
+
.lks-ui .lks-toast{background:var(--surface-0);color:var(--ink-strong);border:1px solid var(--border);box-shadow:0 8px 24px -4px rgb(var(--shadow-rgb)/0.18),0 2px 6px -2px rgb(var(--shadow-rgb)/0.12)}
|
|
136
|
+
.lks-ui .lks-toast .lks-toast-glyph.ok{color:var(--brand)}
|
|
137
|
+
.lks-ui .lks-toast .lks-toast-glyph.warn{color:var(--gold)}
|
|
138
|
+
.lks-ui .lks-toast .lks-toast-glyph.err{color:var(--warning)}
|
|
139
|
+
.lks-ui .lks-toast .lks-toast-glyph.info{color:var(--accent)}
|
|
140
|
+
.lks-ui .lks-toast-action{color:var(--brand)}
|
|
141
|
+
|
|
142
|
+
/* ── rail: signpost section heads + due box ── */
|
|
143
|
+
.lks-ui .lks14-sechead{background:rgb(var(--surface-rail-rgb)/0.55);backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);border:1px solid rgb(var(--gold-rgb)/0.18);border-top-color:rgb(var(--gold-rgb)/0.35);border-radius:.75rem;padding:7px 10px;margin:12px 0 6px;box-shadow:0 2px 8px rgb(var(--shadow-rgb)/0.35),inset 0 1px 0 rgb(255 255 255/0.06);color:var(--gold-light)}
|
|
144
|
+
.lks-ui .lks14-secnum{background:rgb(var(--gold-rgb)/0.15);color:var(--gold-light)}
|
|
145
|
+
.lks-ui .lks14-node{color:var(--ink);border-radius:.75rem}
|
|
146
|
+
.lks-ui .lks14-node:hover{background:rgb(var(--ink-rgb)/0.05)}
|
|
147
|
+
.lks-ui .lks14-node.focus{background:rgb(var(--accent-rgb)/0.1);outline:1px solid rgb(var(--accent-rgb)/0.25)}
|
|
148
|
+
.lks-ui .lks14-node[aria-disabled='true']{color:var(--ink-faint)}
|
|
149
|
+
.lks-ui .lks14-search,.lks-ui .lks-set-select{background:rgb(var(--ink-rgb)/0.05);border:1px solid var(--border-faint);color:var(--ink-strong)}
|
|
150
|
+
.lks-ui .lks14-search:focus{border-color:var(--accent)}
|
|
151
|
+
.lks-ui .lks14-duebox{background:var(--review-tint);border:1px solid rgb(var(--review-rgb)/0.25);color:var(--ink)}
|
|
152
|
+
.lks-ui .lks14-over{color:var(--warning-light)}
|
|
153
|
+
.lks-ui .lks14-tag.weak{color:var(--gold-light)}
|
|
154
|
+
.lks-ui .lks14-masterybar{background:rgb(var(--ink-rgb)/0.08)}
|
|
155
|
+
.lks-ui .lks14-masterybar i{background:var(--gold)}
|
|
156
|
+
.lks-ui .lks14-bar{background:rgb(var(--ink-rgb)/0.08)}
|
|
157
|
+
.lks-ui .lks14-bar i{background:var(--gold)}
|
|
158
|
+
.lks-ui .lks14-pill{color:var(--ink-muted);border-radius:999px}
|
|
159
|
+
.lks-ui .lks14-pill.on{background:rgb(var(--accent-rgb)/0.12);color:var(--accent-light)}
|
|
160
|
+
|
|
161
|
+
/* ── notebook: teach prose + view tabs ── */
|
|
162
|
+
.lks-ui .lks14-viewtab{color:var(--ink-muted);border-radius:999px}
|
|
163
|
+
.lks-ui .lks14-viewtab.on{background:rgb(var(--ink-rgb)/0.08);color:var(--ink-strong)}
|
|
164
|
+
.lks-ui .lks14-prose{color:var(--ink);font-size:.9375rem;line-height:1.65}
|
|
165
|
+
.lks-ui .lks14-prose h1,.lks-ui .lks-note-text h1{font-size:1.25rem;font-weight:800;color:var(--ink-strong)}
|
|
166
|
+
.lks-ui .lks14-prose h2,.lks-ui .lks-note-text h2{font-size:1.1rem;font-weight:700;color:var(--ink-strong);border-bottom:1px solid var(--border);padding-bottom:.3em}
|
|
167
|
+
.lks-ui .lks14-prose h3,.lks-ui .lks-note-text h3{font-size:1rem;font-weight:700;color:var(--ink)}
|
|
168
|
+
.lks-ui .lks14-prose pre,.lks-ui .lks-note-text pre{background:var(--surface-0);border:1px solid var(--border);color:var(--ink)}
|
|
169
|
+
.lks-ui .lks14-prose code,.lks-ui .lks-note-text code{background:rgb(var(--surface-3-rgb)/0.6);color:var(--accent-light)}
|
|
170
|
+
.lks-ui .lks14-prose blockquote{border-left:3px solid var(--accent);background:rgb(var(--accent-rgb)/0.06);color:var(--ink-muted)}
|
|
171
|
+
.lks-ui .lks14-prose table thead{border-bottom:2px solid var(--brand)}
|
|
172
|
+
.lks-ui .lks14-prose th{color:var(--ink-strong)}
|
|
173
|
+
.lks-ui .lks14-prose tbody tr:nth-child(odd){background:rgb(var(--surface-3-rgb)/0.3)}
|
|
174
|
+
.lks-ui .lks14-prose td,.lks-ui .lks14-prose th{border-color:var(--border)}
|
|
175
|
+
.lks-ui .lks-note{background:var(--surface-1);border:1px solid var(--border-faint)}
|
|
176
|
+
.lks-ui .lks-note .lks-note-q{border-left-color:var(--gold)}
|
|
177
|
+
.lks-ui .lks14-zoneh{color:var(--ink-muted)}
|
|
178
|
+
.lks-ui mark.lks-hl{background:rgb(var(--gold-rgb)/0.25);border-bottom:2px solid var(--gold)}
|
|
179
|
+
|
|
180
|
+
/* ── artifact + rate + proposal cards on surface-card language ── */
|
|
181
|
+
.lks-ui .lks-acard,.lks-ui .lks-qcard{background:var(--surface-2);border:1px solid var(--border-faint);border-radius:1rem;box-shadow:0 1px 3px -1px rgb(var(--shadow-rgb)/0.08)}
|
|
182
|
+
.lks-ui .lks-acard-head,.lks-ui .lks-qcard-head{color:var(--ink-muted)}
|
|
183
|
+
.lks-ui .lks-acard-table th{background:transparent;border-bottom:2px solid var(--brand);color:var(--ink-strong)}
|
|
184
|
+
.lks-ui .lks-acard-table td{border-bottom:1px solid var(--border)}
|
|
185
|
+
.lks-ui .lks-acard-code{background:var(--surface-0);border:1px solid var(--border)}
|
|
186
|
+
.lks-ui .lks-acard-line i{color:var(--ink-faint)}
|
|
187
|
+
.lks-ui .lks-acard-note{border-left-color:var(--accent);color:var(--ink-muted)}
|
|
188
|
+
.lks-ui .lks-acard-modal-body{background:var(--surface-0);border-color:var(--border)}
|
|
189
|
+
.lks-ui .lks-acard-guess-opt{background:rgb(var(--ink-rgb)/0.05);border:2px solid transparent;color:var(--ink)}
|
|
190
|
+
.lks-ui .lks-acard-guess-opt:not(:disabled):hover{border-color:var(--accent)}
|
|
191
|
+
.lks-ui .lks-acard-guess-opt.picked{border-color:var(--accent);background:rgb(var(--accent-rgb)/0.1);color:var(--accent-light)}
|
|
192
|
+
.lks-ui .lks-ratecard{background:rgb(var(--review-rgb)/0.08);border:1px solid rgb(var(--review-rgb)/0.25)}
|
|
193
|
+
.lks-ui .lks-ratecard-title{color:var(--ink-strong)}
|
|
194
|
+
.lks-ui .lks-ratecard-opt{background:rgb(var(--ink-rgb)/0.05);border:2px solid transparent;color:var(--ink)}
|
|
195
|
+
.lks-ui .lks-ratecard-opt:not(:disabled):hover{border-color:var(--brand)}
|
|
196
|
+
.lks-ui .lks-ratecard-opt.best{background:var(--gold);color:#3A2E00;box-shadow:0 3px 0 0 var(--gold-dark);border-color:transparent}
|
|
197
|
+
.lks-ui .lks-ratecard-opt.best:active{transform:translateY(2px);box-shadow:0 1px 0 0 var(--gold-dark)}
|
|
198
|
+
.lks-ui .lks-propbanner{background:rgb(var(--gold-rgb)/0.06);border:1px solid rgb(var(--gold-rgb)/0.3);color:var(--ink)}
|
|
199
|
+
.lks-ui .lks-viewtab-badge{background:var(--brand);color:#fff}
|
|
200
|
+
|
|
201
|
+
/* ── the empty states + readbar + switcher ── */
|
|
202
|
+
.lks-ui .lks14-empty{color:var(--ink-muted)}
|
|
203
|
+
.lks-ui .lks-readbar-cur{border-left-color:var(--accent);color:var(--ink-muted)}
|
|
204
|
+
.lks-ui .lks14-switch{background:rgb(var(--ink-rgb)/0.06);border:1px solid var(--border-faint)}
|
|
205
|
+
.lks-ui .lks14-switch-btn.on{background:var(--brand);color:#fff}
|
|
206
|
+
.lks-ui .lks14-searchpanel{background:var(--surface-0);border:1px solid var(--border);box-shadow:0 8px 24px -4px rgb(var(--shadow-rgb)/0.18)}
|
|
207
|
+
.lks-ui .lks14-searchrow:hover{background:rgb(var(--ink-rgb)/0.05)}
|
|
208
|
+
.lks-ui .lks14-searchrow-snip{color:var(--ink-muted)}
|
|
209
|
+
.lks-ui .lks-propcard-err{color:var(--warning-light)}
|
|
210
|
+
.lks-ui .lks14-thinking{color:var(--ink-muted)}
|
|
211
|
+
|
|
212
|
+
/* ══════════ P9b: the balloon course map (upstream v0.28 MapRail, static path) ══════════ */
|
|
213
|
+
.lks-ui{--exam-locked-mix:#A7A2B2}
|
|
214
|
+
/* the sky: upstream's canvas presets reduced to layered gradients (essence port;
|
|
215
|
+
deterministic per-course via pickSky) */
|
|
216
|
+
.lks-ui .lks-map{position:relative;min-height:100%;background-repeat:no-repeat}
|
|
217
|
+
.lks-ui .lks-sky-day{background-image:
|
|
218
|
+
radial-gradient(1.5px 1.5px at 18% 12%,rgb(255 255 255/0.5),transparent 100%),
|
|
219
|
+
radial-gradient(1px 1px at 64% 8%,rgb(255 255 255/0.35),transparent 100%),
|
|
220
|
+
radial-gradient(1px 1px at 82% 22%,rgb(255 255 255/0.3),transparent 100%),
|
|
221
|
+
radial-gradient(120px 60px at 78% 6%,rgb(124 172 255/0.08),transparent 100%),
|
|
222
|
+
linear-gradient(to bottom,#16233F 0%,#0F1B33 55%,var(--surface-rail) 100%)}
|
|
223
|
+
.lks-ui .lks-sky-dusk{background-image:
|
|
224
|
+
radial-gradient(1.5px 1.5px at 24% 10%,rgb(255 255 255/0.45),transparent 100%),
|
|
225
|
+
radial-gradient(1px 1px at 70% 16%,rgb(255 255 255/0.3),transparent 100%),
|
|
226
|
+
radial-gradient(140px 70px at 22% 10%,rgb(255 160 90/0.07),transparent 100%),
|
|
227
|
+
linear-gradient(to bottom,#2A2140 0%,#1D1A38 50%,var(--surface-rail) 100%)}
|
|
228
|
+
.lks-ui .lks-sky-night{background-image:
|
|
229
|
+
radial-gradient(1.5px 1.5px at 12% 18%,rgb(255 255 255/0.55),transparent 100%),
|
|
230
|
+
radial-gradient(1px 1px at 38% 6%,rgb(255 255 255/0.4),transparent 100%),
|
|
231
|
+
radial-gradient(1px 1px at 58% 24%,rgb(255 255 255/0.35),transparent 100%),
|
|
232
|
+
radial-gradient(1.5px 1.5px at 88% 10%,rgb(255 255 255/0.5),transparent 100%),
|
|
233
|
+
radial-gradient(1px 1px at 76% 30%,rgb(255 255 255/0.3),transparent 100%),
|
|
234
|
+
radial-gradient(90px 90px at 84% 8%,rgb(200 214 255/0.06),transparent 100%),
|
|
235
|
+
linear-gradient(to bottom,#0B1024 0%,#0A0D1C 55%,var(--surface-rail) 100%)}
|
|
236
|
+
.lks-ui .lks-mapsec{padding:12px 8px 4px}
|
|
237
|
+
/* the signpost (map-signpost verbatim): frosted board with a gold rim */
|
|
238
|
+
.lks-ui .lks-signpost{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border-radius:8px;font:inherit;cursor:pointer;text-align:left;
|
|
239
|
+
background:rgb(var(--surface-rail-rgb)/0.55);backdrop-filter:blur(7px);-webkit-backdrop-filter:blur(7px);
|
|
240
|
+
border:1px solid rgb(var(--gold-rgb)/0.18);border-top-color:rgb(var(--gold-rgb)/0.35);
|
|
241
|
+
box-shadow:0 2px 8px rgb(var(--shadow-rgb)/0.35),inset 0 1px 0 rgb(255 255 255/0.06)}
|
|
242
|
+
.lks-ui .lks-signpost-num{width:24px;height:24px;border-radius:999px;background:var(--gold);color:#201500;font-size:11px;font-weight:800;display:flex;align-items:center;justify-content:center;flex:none;box-shadow:0 0 0 2px rgb(var(--gold-rgb)/0.4)}
|
|
243
|
+
.lks-ui .lks-signpost-title{flex:1;font-size:12.5px;font-weight:700;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-shadow:0 1px 2px rgba(0,0,0,0.7)}
|
|
244
|
+
.lks-ui .lks-signpost-caret{color:rgb(var(--ink-rgb)/0.5);font-size:10px}
|
|
245
|
+
.lks-ui .lks-mapfield{position:relative}
|
|
246
|
+
.lks-ui .lks-mapropes{position:absolute;inset:0;width:100%;pointer-events:none;overflow:visible}
|
|
247
|
+
.lks-ui .lks-mapnode{position:absolute;width:110px;display:flex;flex-direction:column;align-items:center;
|
|
248
|
+
animation:lks-balloon-bob var(--bob-duration,6s) ease-in-out infinite;animation-delay:var(--bob-delay,0s)}
|
|
249
|
+
@keyframes lks-balloon-bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}
|
|
250
|
+
@keyframes lks-path-draw{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
|
|
251
|
+
@media (prefers-reduced-motion: reduce){.lks-ui .lks-mapnode{animation:none}.lks-ui .lks-bubble-available{animation:none}}
|
|
252
|
+
/* the bubbles (lesson-bubble verbatim): 3D sphere lighting + ::before inner highlight */
|
|
253
|
+
.lks-ui .lks-bubble{position:relative;width:56px;height:56px;border-radius:999px;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;padding:0;transition:all .2s var(--ease-out-back);background-position:30% 25%}
|
|
254
|
+
.lks-ui .lks-bubble::before{content:'';position:absolute;top:12%;left:22%;width:38%;height:28%;border-radius:50%;
|
|
255
|
+
background:radial-gradient(ellipse at center,var(--inner-highlight) 0%,rgb(255 255 255/0) 70%);pointer-events:none;z-index:1}
|
|
256
|
+
.lks-ui .lks-bubble[aria-disabled='true']{cursor:not-allowed}
|
|
257
|
+
.lks-ui .lks-bubble.selected{outline:4px solid var(--accent);outline-offset:3px}
|
|
258
|
+
.lks-ui .lks-bubble-glyph{position:relative;z-index:2;color:#fff;display:inline-flex;filter:drop-shadow(0 1px 2px rgba(0,0,0,0.4))}
|
|
259
|
+
.lks-ui .lks-bubble-glyph.dim{opacity:.5}
|
|
260
|
+
.lks-ui .lks-bubble-ring{position:absolute;inset:0;width:100%;height:100%;transform:rotate(-90deg);pointer-events:none}
|
|
261
|
+
.lks-ui .lks-bubble-due{position:absolute;top:-4px;right:-4px;width:16px;height:16px;border-radius:999px;background:var(--review);color:#fff;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center;box-shadow:0 0 0 2px var(--surface-rail);z-index:3}
|
|
262
|
+
.lks-ui .lks-bubble-name{margin-top:4px;max-width:120px;font-size:11px;font-weight:700;color:#fff;background:rgb(var(--brand-rgb)/0.9);padding:2px 6px;border-radius:6px;text-align:center;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
263
|
+
/* locked: heavy sunken stone */
|
|
264
|
+
.lks-ui .lks-bubble-locked{background:radial-gradient(circle at 35% 30%,var(--surface-3) 0%,var(--surface-2) 60%,var(--surface-1) 100%);
|
|
265
|
+
box-shadow:inset 0 3px 6px rgb(var(--shadow-rgb)/0.5),inset 0 -2px 4px rgb(255 255 255/0.04),0 2px 4px rgb(var(--shadow-rgb)/0.3)}
|
|
266
|
+
.lks-ui .lks-bubble-locked::before{background:radial-gradient(ellipse at center,rgb(255 255 255/0.12) 0%,rgb(255 255 255/0) 70%)}
|
|
267
|
+
/* available: bright glowing sphere, your move */
|
|
268
|
+
.lks-ui .lks-bubble-available{background:radial-gradient(circle at 32% 28%,var(--brand-light) 0%,var(--brand) 45%,var(--brand-dark) 100%);
|
|
269
|
+
box-shadow:0 0 0 4px var(--brand-ring),0 0 16px var(--brand-glow),0 4px 10px rgb(var(--shadow-rgb)/0.25)}
|
|
270
|
+
.lks-ui .lks-bubble-available:hover{transform:translateY(-3px) scale(1.03);
|
|
271
|
+
box-shadow:0 0 0 5px rgb(var(--brand-rgb)/0.2),0 0 22px rgb(var(--brand-rgb)/0.5),0 6px 14px rgb(var(--shadow-rgb)/0.3)}
|
|
272
|
+
.lks-ui .lks-bubble-available:active{transform:translateY(2px) scale(0.97);
|
|
273
|
+
box-shadow:0 0 0 3px var(--brand-ring),0 0 10px rgb(var(--brand-rgb)/0.3),0 2px 6px rgb(var(--shadow-rgb)/0.2)}
|
|
274
|
+
@keyframes lks-bubble-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.04)}}
|
|
275
|
+
.lks-ui .lks-bubble-available{animation:lks-bubble-pulse 2.4s ease-in-out infinite}
|
|
276
|
+
/* in_progress: blue in motion */
|
|
277
|
+
.lks-ui .lks-bubble-in-progress{background:radial-gradient(circle at 32% 28%,var(--accent-light) 0%,var(--accent) 45%,var(--accent-dark) 100%);
|
|
278
|
+
box-shadow:0 0 0 4px var(--accent-ring),0 0 14px var(--accent-glow),0 4px 10px rgb(var(--shadow-rgb)/0.25)}
|
|
279
|
+
.lks-ui .lks-bubble-in-progress:hover{transform:translateY(-2px) scale(1.03);
|
|
280
|
+
box-shadow:0 0 0 5px rgb(var(--accent-rgb)/0.2),0 0 20px rgb(var(--accent-rgb)/0.45),0 6px 14px rgb(var(--shadow-rgb)/0.3)}
|
|
281
|
+
/* mastered: gold crown halo, scaled up */
|
|
282
|
+
.lks-ui .lks-bubble-mastered{background:radial-gradient(circle at 32% 28%,var(--gold-light) 0%,var(--gold) 45%,var(--gold-dark) 100%);
|
|
283
|
+
box-shadow:0 0 0 4px var(--gold-ring),0 0 22px var(--gold-glow),0 0 40px rgb(var(--gold-rgb)/0.15),0 4px 12px rgb(var(--shadow-rgb)/0.25);transform:scale(1.1)}
|
|
284
|
+
.lks-ui .lks-bubble-mastered::before{background:radial-gradient(ellipse at center,rgb(255 255 255/0.7) 0%,rgb(255 255 255/0) 70%)}
|
|
285
|
+
.lks-ui .lks-bubble-mastered:hover{transform:scale(1.13) translateY(-2px);
|
|
286
|
+
box-shadow:0 0 0 5px rgb(var(--gold-rgb)/0.25),0 0 28px rgb(var(--gold-rgb)/0.55),0 0 50px rgb(var(--gold-rgb)/0.2),0 6px 16px rgb(var(--shadow-rgb)/0.3)}
|
|
287
|
+
/* exam: the section boss */
|
|
288
|
+
.lks-ui .lks-exam{background:radial-gradient(circle at 32% 28%,var(--exam-light) 0%,var(--exam) 45%,var(--exam-dark) 100%);
|
|
289
|
+
box-shadow:0 0 0 4px var(--exam-ring),0 0 16px var(--exam-glow),0 4px 10px rgb(var(--shadow-rgb)/0.25)}
|
|
290
|
+
.lks-ui .lks-exam-locked{background:radial-gradient(circle at 35% 30%,var(--exam-locked) 0%,var(--exam-locked-mix) 60%,var(--surface-1) 100%);
|
|
291
|
+
box-shadow:inset 0 3px 6px rgb(var(--shadow-rgb)/0.5),inset 0 -2px 4px rgb(255 255 255/0.04),0 2px 4px rgb(var(--shadow-rgb)/0.3)}
|
|
292
|
+
.lks-ui .lks-exam-passed{background:radial-gradient(circle at 32% 28%,rgb(var(--exam-light-rgb,192 132 252)) 0%,var(--exam-light) 40%,var(--exam) 100%);
|
|
293
|
+
box-shadow:0 0 0 4px var(--gold-ring),0 0 18px var(--exam-glow),0 0 28px rgb(var(--gold-rgb)/0.15),0 4px 12px rgb(var(--shadow-rgb)/0.25)}
|
|
294
|
+
/* the floating rail head card (upstream map-header card) */
|
|
295
|
+
.lks-ui .lks14-railhead{background:rgb(var(--surface-rail-rgb)/0.55);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid rgb(255 255 255/0.06);border-radius:12px;padding:8px 10px}
|
|
296
|
+
.lks-ui .lks14-railtitle{color:#fff;font-weight:800;font-size:.875rem;text-shadow:0 1px 3px rgba(0,0,0,0.6)}
|
|
297
|
+
.lks-ui .lks14-railsub{color:rgb(var(--ink-rgb)/0.6)}
|
|
298
|
+
.lks-ui .lks14-masterybar{height:10px;background:rgb(0 0 0/0.4);border-radius:999px;box-shadow:inset 0 0 0 1px rgb(255 255 255/0.1);overflow:hidden}
|
|
299
|
+
.lks-ui .lks14-masterybar i{background:var(--brand)}
|
|
300
|
+
.lks-ui .lks14-masterybar.gold i{background:var(--gold)}
|
|
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
|
+
.lks-ui .lks14-search:focus{border-color:var(--brand);outline:none}
|
|
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}
|
|
454
|
+
`;
|
|
455
|
+
//#endregion
|
|
21
456
|
//#region src/client/styles.ts
|
|
22
457
|
/**
|
|
458
|
+
* The study stylesheet. The center-takeover visibility follows the stardeck
|
|
459
|
+
* panel doctrine: the shell view is display:none until the `<html>` active
|
|
460
|
+
* attribute flips, and the host conversation column's own children are hidden
|
|
461
|
+
* behind `!important` so React never fights the takeover.
|
|
462
|
+
*/
|
|
463
|
+
/**
|
|
23
464
|
* Style injection for the study surfaces: one `<style>` element carrying the
|
|
24
465
|
* `.lks14-*` panel classes (the sidebar-entry center-takeover, arranged like
|
|
25
466
|
* upstream LookatStudy) plus the shared `.lks-*` classes the host-native
|
|
@@ -33,12 +474,6 @@ window.__ModuleLoader__.load({
|
|
|
33
474
|
*/
|
|
34
475
|
/** Marker attribute proving the stylesheet is already in the document. */
|
|
35
476
|
const STYLE_ID = "data-dsh-plugin-lookatstudy";
|
|
36
|
-
/**
|
|
37
|
-
* The study stylesheet. The center-takeover visibility follows the stardeck
|
|
38
|
-
* panel doctrine: the shell view is display:none until the `<html>` active
|
|
39
|
-
* attribute flips, and the host conversation column's own children are hidden
|
|
40
|
-
* behind `!important` so React never fights the takeover.
|
|
41
|
-
*/
|
|
42
477
|
const STUDY_CSS = `
|
|
43
478
|
/* State inks: small colored text must clear WCAG AA (4.5:1), but the host's
|
|
44
479
|
raw state tokens (success/warn primaries) sit at 2.3–2.6:1 on light
|
|
@@ -74,11 +509,12 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
74
509
|
.lks14{position:relative;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);overflow:hidden;font-family:var(--dsw-font-family)}
|
|
75
510
|
.lks14 button{font-family:var(--dsw-font-family);cursor:pointer}
|
|
76
511
|
.lks14-body{display:flex;flex:1;min-width:0;min-height:0}
|
|
512
|
+
.lks14-righthalf{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}
|
|
513
|
+
.lks14-row{flex:1;min-height:0;display:flex}
|
|
77
514
|
.lks14-col{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}
|
|
78
|
-
.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)}
|
|
79
515
|
|
|
80
516
|
/* 左 rail: course picker, tree, review, import */
|
|
81
|
-
.lks14-rail{flex:0 0
|
|
517
|
+
.lks14-rail{flex:0 0 300px;border-right:none;padding:0;overflow:hidden;position:relative}
|
|
82
518
|
.lks14-railhead{display:flex;align-items:center;gap:6px;margin-top:10px}
|
|
83
519
|
.lks14-railtitle{font-weight:600;font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
|
|
84
520
|
.lks14-railsub{font-size:12px;color:var(--dsw-alias-label-secondary);margin:6px 0}
|
|
@@ -90,35 +526,19 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
90
526
|
.lks14-duebox{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;margin:8px 0;font-size:12.5px}
|
|
91
527
|
.lks14-dueitem{display:flex;justify-content:space-between;gap:6px;padding:2px 0;color:var(--dsw-alias-label-secondary)}
|
|
92
528
|
.lks14-over{color:var(--dsw-alias-state-error-primary)}
|
|
93
|
-
.lks14-sechead{display:flex;align-items:center;gap:6px;width:100%;background:none;border:none;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12.5px;font-weight:600;padding:6px 2px;cursor:pointer;text-align:left}
|
|
94
|
-
.lks14-sechead:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
|
|
95
|
-
.lks14-secnum{flex:none;width:18px;height:18px;border-radius:5px;background:var(--dsw-alias-bg-layer-2);display:inline-flex;align-items:center;justify-content:center;font-size:11px}
|
|
96
|
-
.lks14-secheadt{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
97
|
-
.lks14-node{display:flex;align-items:center;gap:6px;width:100%;background:none;border:none;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;padding:5px 6px;border-radius:8px;cursor:pointer;text-align:left}
|
|
98
|
-
.lks14-node:hover{background:var(--dsw-alias-bg-layer-1)}
|
|
99
|
-
.lks14-node:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
|
|
100
|
-
.lks14-node.focus{background:var(--dsw-alias-bg-layer-2)}
|
|
101
|
-
.lks14-node[aria-disabled='true']{color:var(--dsw-alias-label-tertiary);cursor:default}
|
|
102
|
-
.lks14-g{flex:none;display:inline-flex}
|
|
103
|
-
.lks14-t{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
104
|
-
.lks14-tag{flex:none;display:inline-flex;align-items:center;gap:2px;font-size:10.5px;border-radius:6px;padding:1px 4px}
|
|
105
|
-
.lks14-tag.weak{color:var(--dsw-alias-state-warn-primary)}
|
|
106
|
-
.lks14-bar{flex:none;width:26px;height:4px;border-radius:2px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}
|
|
107
|
-
.lks14-bar i{display:block;height:100%;background:var(--dsw-alias-business-primary);transform-origin:left}
|
|
108
|
-
.lks14-pct{flex:none;font-size:10.5px;color:var(--dsw-alias-label-tertiary)}
|
|
109
529
|
.lks14-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;padding:18px 6px;line-height:1.7}
|
|
110
530
|
.lks14-import{margin:8px 0}
|
|
111
531
|
.lks14-inputrow{display:flex;gap:6px}
|
|
112
532
|
.lks14-hint{font-size:11.5px;color:var(--dsw-alias-label-tertiary);margin-top:6px;line-height:1.6}
|
|
113
533
|
|
|
114
534
|
/* 中 chat: the tutor stream + its own composer (upstream ChatStream/ChatComposer) */
|
|
115
|
-
.lks14-chat{flex:
|
|
535
|
+
.lks14-chat{flex:0 0 auto;width:clamp(480px,45%,800px);min-width:0;border-right:none}
|
|
116
536
|
.lks14-chatlesson{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:400;color:var(--dsw-alias-label-tertiary)}
|
|
117
537
|
.lks14-pills{display:inline-flex;gap:2px}
|
|
118
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}
|
|
119
539
|
.lks14-pill:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
|
|
120
540
|
.lks14-pill.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
121
|
-
.lks14-stream{flex:1;overflow-y:auto;padding:
|
|
541
|
+
.lks14-stream{flex:1;overflow-y:auto;padding:20px 20px 24px;display:flex;flex-direction:column;gap:24px}
|
|
122
542
|
.lks14-msg{max-width:92%;line-height:1.65;font-size:13.5px}
|
|
123
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}
|
|
124
544
|
.lks14-msg-assistant{align-self:flex-start;background:var(--dsw-alias-bg-layer-1);border-radius:12px 12px 12px 3px;padding:8px 12px}
|
|
@@ -133,7 +553,7 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
133
553
|
.lks14-opt:hover{background:var(--dsw-alias-bg-layer-2)}
|
|
134
554
|
.lks14-opt:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
|
|
135
555
|
.lks14-optletter{flex:none;font-weight:700;color:var(--dsw-alias-business-primary)}
|
|
136
|
-
.lks14-starters{flex:none;display:flex;flex-wrap:
|
|
556
|
+
.lks14-starters{flex:none;display:flex;flex-wrap:nowrap;gap:6px;padding:0 20px 6px;overflow-x:auto}
|
|
137
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}
|
|
138
558
|
.lks14-starter:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}
|
|
139
559
|
.lks14-starter:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
|
|
@@ -143,11 +563,11 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
143
563
|
.lks14-composertext:focus{outline:none;border-color:var(--dsw-alias-state-business-primary)}
|
|
144
564
|
|
|
145
565
|
/* 右 notebook: 讲解/概念图/笔记 (upstream NotebookPanel) */
|
|
146
|
-
.lks14-note{flex:
|
|
566
|
+
.lks14-note{flex:1 1 auto;min-width:440px;padding:0;overflow-y:auto}
|
|
147
567
|
.lks14-lessonhead{margin:10px 0 6px}
|
|
148
568
|
.lks14-lessonhead h2{margin:0 0 4px;font-size:16px}
|
|
149
569
|
.lks14-meta{font-size:12px;color:var(--dsw-alias-label-secondary);margin:2px 0}
|
|
150
|
-
.lks14-viewtabs{display:flex;gap:2px;margin:
|
|
570
|
+
.lks14-viewtabs{display:flex;gap:2px;margin:0 0 12px}
|
|
151
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}
|
|
152
572
|
.lks14-viewtab:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-state-business-primary);outline:none}
|
|
153
573
|
.lks14-viewtab.on{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}
|
|
@@ -218,9 +638,9 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
218
638
|
.lks14-switch{display:flex}
|
|
219
639
|
.lks14-body{flex-direction:column}
|
|
220
640
|
.lks14-rail{flex:none;max-height:32vh;border-right:none;border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
221
|
-
.lks14-chat{min-width:0}
|
|
641
|
+
.lks14-chat{min-width:0;width:auto;flex:1 1 auto}
|
|
222
642
|
.lks14-note{flex:1 1 auto;min-height:0}
|
|
223
|
-
.lks14-body[data-pane='rail'] .lks14-
|
|
643
|
+
.lks14-body[data-pane='rail'] .lks14-righthalf{display:none}
|
|
224
644
|
.lks14-body[data-pane='chat'] .lks14-rail,.lks14-body[data-pane='chat'] .lks14-note,
|
|
225
645
|
.lks14-body[data-pane='note'] .lks14-rail,.lks14-body[data-pane='note'] .lks14-chat{display:none}
|
|
226
646
|
.lks14-body[data-pane='rail'] .lks14-rail{display:flex;max-height:none;flex:1 1 auto}
|
|
@@ -394,7 +814,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
394
814
|
if (existing !== null) return existing;
|
|
395
815
|
const style = document.createElement("style");
|
|
396
816
|
style.setAttribute(STYLE_ID, "");
|
|
397
|
-
style.textContent = STUDY_CSS;
|
|
817
|
+
style.textContent = STUDY_CSS + UPSTREAM_CSS;
|
|
398
818
|
document.head.appendChild(style);
|
|
399
819
|
return style;
|
|
400
820
|
}
|
|
@@ -619,6 +1039,51 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
619
1039
|
fill: "currentColor"
|
|
620
1040
|
}));
|
|
621
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
|
+
}
|
|
622
1087
|
//#endregion
|
|
623
1088
|
//#region src/client/data.ts
|
|
624
1089
|
/**
|
|
@@ -1656,8 +2121,8 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1656
2121
|
"col.tutor": "导师",
|
|
1657
2122
|
"col.bb": "黑板",
|
|
1658
2123
|
"pane.rail": "课程",
|
|
1659
|
-
"pane.
|
|
1660
|
-
"pane.
|
|
2124
|
+
"pane.chat": "对话",
|
|
2125
|
+
"pane.note": "黑板",
|
|
1661
2126
|
"viewtab.teach": "讲解",
|
|
1662
2127
|
"viewtab.cmap": "概念图",
|
|
1663
2128
|
"viewtab.cmap.title": "本课概念之间的关系图",
|
|
@@ -1724,9 +2189,21 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1724
2189
|
"proposal.accept.msg": "我接受《{lesson}》的掌握度提议,帮我标记为掌握。",
|
|
1725
2190
|
"proposal.decline.msg": "《{lesson}》我先再练练,暂不接受提前毕业。",
|
|
1726
2191
|
"tutor.thinking": "导师思考中…",
|
|
1727
|
-
"
|
|
1728
|
-
"
|
|
1729
|
-
"
|
|
2192
|
+
"header.xp": "经验值 {xp}",
|
|
2193
|
+
"header.streak": "连续学习天数",
|
|
2194
|
+
"header.level": "等级",
|
|
2195
|
+
"map.node.locked": "锁定 — 先完成前面的课时",
|
|
2196
|
+
"map.node.due": "到了复习时间",
|
|
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": "风格:",
|
|
1730
2207
|
"companion.poke": "戳一戳伴学伙伴",
|
|
1731
2208
|
"companion.poked": "伴学伙伴向你眨了眨眼",
|
|
1732
2209
|
"companion.form.ember": "小焰",
|
|
@@ -1754,6 +2231,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1754
2231
|
"rail.due": "待复习 {count}",
|
|
1755
2232
|
"rail.due.over": "超{days}天",
|
|
1756
2233
|
"rail.due.start": "开始复习",
|
|
2234
|
+
"rail.due.none": "暂时没有到期的复习,保持节奏!",
|
|
1757
2235
|
"rail.due.tag": "这课时的复习今天到期(SM-2)",
|
|
1758
2236
|
"rail.delete": "删除本课程",
|
|
1759
2237
|
"rail.delete.confirm": "确认删除?",
|
|
@@ -1856,8 +2334,8 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1856
2334
|
"col.tutor": "Tutor",
|
|
1857
2335
|
"col.bb": "Blackboard",
|
|
1858
2336
|
"pane.rail": "Courses",
|
|
1859
|
-
"pane.
|
|
1860
|
-
"pane.
|
|
2337
|
+
"pane.chat": "Chat",
|
|
2338
|
+
"pane.note": "Board",
|
|
1861
2339
|
"viewtab.teach": "Teach",
|
|
1862
2340
|
"viewtab.cmap": "Concepts",
|
|
1863
2341
|
"viewtab.cmap.title": "The lesson's concepts relate",
|
|
@@ -1924,9 +2402,21 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1924
2402
|
"proposal.accept.msg": "I accept the mastery proposal for \"{lesson}\" — mark it mastered.",
|
|
1925
2403
|
"proposal.decline.msg": "I want more practice on \"{lesson}\" before graduating.",
|
|
1926
2404
|
"tutor.thinking": "The tutor is thinking…",
|
|
1927
|
-
"
|
|
1928
|
-
"
|
|
1929
|
-
"
|
|
2405
|
+
"header.xp": "{xp} XP",
|
|
2406
|
+
"header.streak": "Day streak",
|
|
2407
|
+
"header.level": "Level",
|
|
2408
|
+
"map.node.locked": "Locked — finish the previous lessons first",
|
|
2409
|
+
"map.node.due": "Due for review",
|
|
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: ",
|
|
1930
2420
|
"companion.poke": "Poke the companion",
|
|
1931
2421
|
"companion.poked": "The companion winks at you",
|
|
1932
2422
|
"companion.form.ember": "Ember",
|
|
@@ -1954,6 +2444,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1954
2444
|
"rail.due": "{count} due",
|
|
1955
2445
|
"rail.due.over": "{days}d overdue",
|
|
1956
2446
|
"rail.due.start": "Start reviewing",
|
|
2447
|
+
"rail.due.none": "Nothing due right now — keep the streak!",
|
|
1957
2448
|
"rail.due.tag": "this lesson's review is due today (SM-2)",
|
|
1958
2449
|
"rail.delete": "Delete this course",
|
|
1959
2450
|
"rail.delete.confirm": "Delete?",
|
|
@@ -3098,7 +3589,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3098
3589
|
onClick: () => {
|
|
3099
3590
|
setExpanded(true);
|
|
3100
3591
|
}
|
|
3101
|
-
},
|
|
3592
|
+
}, (0, react.createElement)(IconMaximizeOutline16, { size: 13 }))), body, expanded ? (0, react.createElement)("div", {
|
|
3102
3593
|
className: "lks-acard-modal",
|
|
3103
3594
|
onClick: () => {
|
|
3104
3595
|
setExpanded(false);
|
|
@@ -3113,7 +3604,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3113
3604
|
onClick: () => {
|
|
3114
3605
|
setExpanded(false);
|
|
3115
3606
|
}
|
|
3116
|
-
},
|
|
3607
|
+
}, (0, react.createElement)(IconCloseFill16, { size: 13 }))), failed ? (0, react.createElement)("pre", { className: "lks-acard-code" }, mermaid) : (0, react.createElement)("div", { ref: (el) => {
|
|
3117
3608
|
if (el !== null) renderMermaidInto(el, mermaid, () => {
|
|
3118
3609
|
setFailed(true);
|
|
3119
3610
|
});
|
|
@@ -3592,6 +4083,248 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3592
4083
|
return section.lessons.some((l) => l.focus || l.kind !== "exam" && l.status !== "mastered" && l.status !== "locked");
|
|
3593
4084
|
}
|
|
3594
4085
|
//#endregion
|
|
4086
|
+
//#region src/vendor/map-layout.ts
|
|
4087
|
+
/** 容器宽度(章节内可用宽度,扣除 padding)。MapRail 章节容器 px-2。 */
|
|
4088
|
+
const CONTAINER_WIDTH = 268;
|
|
4089
|
+
const NODE_RADIUS = 28;
|
|
4090
|
+
const NODE_SPACING_Y = 104;
|
|
4091
|
+
const BALLOON_MARGIN = 18;
|
|
4092
|
+
/** 首节点中心下限(防顶撞上方章节路牌)。MapRail 渲染偏移 +12 + NODE_H/2=38 → wrapper 顶 = y-26 ≥ 8。 */
|
|
4093
|
+
const TOP_PAD = 34;
|
|
4094
|
+
/** y 轴抖动幅度(px)。贪心兜底后,jitter 不再是重叠源,仅负责高低参差。 */
|
|
4095
|
+
const Y_JITTER = 26;
|
|
4096
|
+
/** 绳子下垂量(px,模拟重力)。让贝塞尔像被拽下来的绳子,而非 S 形箭头。 */
|
|
4097
|
+
const ROPE_SAG = 22;
|
|
4098
|
+
/**
|
|
4099
|
+
* 确定性字符串哈希(FNV-1a 变体,32-bit)。
|
|
4100
|
+
* 同输入永远同输出 → 同一节点 id 每次渲染算出的抖动相同 → 气球位置稳定不跳。
|
|
4101
|
+
* 用于气球横向抖动 + bob 相位 + 星点位置 + 天体色切。
|
|
4102
|
+
*/
|
|
4103
|
+
function hashStr(s) {
|
|
4104
|
+
let h = 2166136261;
|
|
4105
|
+
for (let i = 0; i < s.length; i++) {
|
|
4106
|
+
h ^= s.charCodeAt(i);
|
|
4107
|
+
h = Math.imul(h, 16777619);
|
|
4108
|
+
}
|
|
4109
|
+
h ^= h >>> 16;
|
|
4110
|
+
h = Math.imul(h, 2246822507);
|
|
4111
|
+
h ^= h >>> 13;
|
|
4112
|
+
h = Math.imul(h, 3266489909);
|
|
4113
|
+
h ^= h >>> 16;
|
|
4114
|
+
return h >>> 0;
|
|
4115
|
+
}
|
|
4116
|
+
/**
|
|
4117
|
+
* 算某 section 的气球节点坐标 + 绳子连接路径。
|
|
4118
|
+
* 容器高度由 nodes 数量决定(调用方据此设容器 min-height)。
|
|
4119
|
+
*
|
|
4120
|
+
* 抖动算法:v0.6 同时在 x 和 y 两轴抖动 → 气球真正"飘"在空中,
|
|
4121
|
+
* 不再是呆板的直线/微弯列。x 大幅抖动(用满容器宽度),y 在基线上下 ±Y_JITTER。
|
|
4122
|
+
* 两个独立哈希(x 用 seed:i,y 用 seed:i:y)→ 抖动方向不相关,分布更自然。
|
|
4123
|
+
*
|
|
4124
|
+
* v0.8 防重叠:抖动后跑一趟**贪心向下推**(确定性,只增 y,保持单调),
|
|
4125
|
+
* 1) 首节点中心不低于 TOP_PAD —— 防顶撞上方章节路牌;
|
|
4126
|
+
* 2) 相邻节点中心纵向距 ≥ MIN_GAP_Y(= 盒高 + 8px 呼吸)—— 视觉盒永不重叠。
|
|
4127
|
+
* 任何 seed / 任何课数都保证不重叠(verify-map-layout T11 跨 240 种子穷举验证)。
|
|
4128
|
+
*/
|
|
4129
|
+
function computeBalloonLayout(lessonCount, containerWidth = CONTAINER_WIDTH, seed = "") {
|
|
4130
|
+
const nodes = [];
|
|
4131
|
+
const centerX = containerWidth / 2;
|
|
4132
|
+
const maxDrift = containerWidth / 2 - BALLOON_MARGIN - NODE_RADIUS;
|
|
4133
|
+
for (let i = 0; i < lessonCount; i++) {
|
|
4134
|
+
const baseY = TOP_PAD + i * NODE_SPACING_Y;
|
|
4135
|
+
const x = centerX + (hashStr(`${seed}:${i}`) / 4294967295 * 2 - 1) * maxDrift;
|
|
4136
|
+
const y = baseY + (hashStr(`${seed}:${i}:y`) / 4294967295 * 2 - 1) * Y_JITTER;
|
|
4137
|
+
nodes.push({
|
|
4138
|
+
x,
|
|
4139
|
+
y,
|
|
4140
|
+
index: i
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
if (nodes.length > 0) {
|
|
4144
|
+
if (nodes[0].y < TOP_PAD) nodes[0].y = TOP_PAD;
|
|
4145
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
4146
|
+
const minY = nodes[i - 1].y + 84;
|
|
4147
|
+
if (nodes[i].y < minY) nodes[i].y = minY;
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
const segments = [];
|
|
4151
|
+
for (let i = 0; i < nodes.length - 1; i++) segments.push({
|
|
4152
|
+
from: nodes[i],
|
|
4153
|
+
to: nodes[i + 1],
|
|
4154
|
+
index: i
|
|
4155
|
+
});
|
|
4156
|
+
return {
|
|
4157
|
+
nodes,
|
|
4158
|
+
segments,
|
|
4159
|
+
height: nodes.length === 0 ? 40 : nodes[nodes.length - 1].y + 38 + 10
|
|
4160
|
+
};
|
|
4161
|
+
}
|
|
4162
|
+
/**
|
|
4163
|
+
* 两节点间的绳子贝塞尔(下垂)。
|
|
4164
|
+
* 控制点:y 中点 + ROPE_SAG(向下垂,模拟重力);x 用各自端点 x。
|
|
4165
|
+
* 比原 S 形软,像被拽下来的绳子。
|
|
4166
|
+
*/
|
|
4167
|
+
function balloonSegmentToPath(seg) {
|
|
4168
|
+
const { from, to } = seg;
|
|
4169
|
+
const controlY = (from.y + to.y) / 2 + ROPE_SAG;
|
|
4170
|
+
return `M ${from.x} ${from.y} C ${from.x} ${controlY}, ${to.x} ${controlY}, ${to.x} ${to.y}`;
|
|
4171
|
+
}
|
|
4172
|
+
//#endregion
|
|
4173
|
+
//#region src/client/maprail.tsx
|
|
4174
|
+
/**
|
|
4175
|
+
* MapRail balloon map (P9b) — the upstream v0.28 course map ported onto the
|
|
4176
|
+
* plugin's rail: deterministic balloon layout (vendored mapLayout engine),
|
|
4177
|
+
* sagging bezier ropes with path-draw on walked segments, state bubbles with
|
|
4178
|
+
* 3D sphere lighting, signpost section heads, and the sky backdrop. This is
|
|
4179
|
+
* upstream's own reduced-motion presentation (static layout + balloon-bob
|
|
4180
|
+
* CSS), which is exactly the deterministic half of its physics map.
|
|
4181
|
+
*/
|
|
4182
|
+
/** Upstream bubbleClass port: the four study states + the three exam variants.
|
|
4183
|
+
* Status→class goes through a table (in_progress → in-progress, like upstream's switch). */
|
|
4184
|
+
const STATUS_CLASS = {
|
|
4185
|
+
locked: "locked",
|
|
4186
|
+
available: "available",
|
|
4187
|
+
in_progress: "in-progress",
|
|
4188
|
+
mastered: "mastered"
|
|
4189
|
+
};
|
|
4190
|
+
function bubbleClasses(lesson, examAllowed) {
|
|
4191
|
+
if (lesson.kind === "exam") {
|
|
4192
|
+
if (!examAllowed) return "lks-bubble lks-exam-locked";
|
|
4193
|
+
return lesson.status === "mastered" ? "lks-bubble lks-exam-passed" : "lks-bubble lks-exam";
|
|
4194
|
+
}
|
|
4195
|
+
return `lks-bubble lks-bubble-${STATUS_CLASS[lesson.status] ?? "locked"}`;
|
|
4196
|
+
}
|
|
4197
|
+
/** A rope segment is "walked" when its FROM node has been opened
|
|
4198
|
+
* (upstream ropeStyleIsPassed: mastered | in_progress | available). */
|
|
4199
|
+
function ropePassed(from) {
|
|
4200
|
+
return from.status === "mastered" || from.status === "in_progress" || from.status === "available";
|
|
4201
|
+
}
|
|
4202
|
+
/** Deterministic sky preset per course (upstream pickPreset's CSS-essence port). */
|
|
4203
|
+
function pickSky(courseId) {
|
|
4204
|
+
return [
|
|
4205
|
+
"day",
|
|
4206
|
+
"dusk",
|
|
4207
|
+
"night"
|
|
4208
|
+
][hashStr(courseId) % 3];
|
|
4209
|
+
}
|
|
4210
|
+
/** One section: signpost head (still the plugin's collapse toggle) + the balloon field. */
|
|
4211
|
+
function MapSectionView({ section, examAllowed, open, onToggle, onJump }) {
|
|
4212
|
+
const pathRef = (0, react.useRef)(null);
|
|
4213
|
+
const [containerW, setContainerW] = (0, react.useState)(268);
|
|
4214
|
+
(0, react.useEffect)(() => {
|
|
4215
|
+
if (pathRef.current === null) return;
|
|
4216
|
+
const measure = () => {
|
|
4217
|
+
const w = pathRef.current?.clientWidth ?? 268;
|
|
4218
|
+
setContainerW(w > 0 ? w : 268);
|
|
4219
|
+
};
|
|
4220
|
+
measure();
|
|
4221
|
+
const ro = new ResizeObserver(measure);
|
|
4222
|
+
ro.observe(pathRef.current);
|
|
4223
|
+
return () => {
|
|
4224
|
+
ro.disconnect();
|
|
4225
|
+
};
|
|
4226
|
+
}, [open]);
|
|
4227
|
+
const lessons = section.lessons;
|
|
4228
|
+
const layout = computeBalloonLayout(lessons.length, containerW, section.title);
|
|
4229
|
+
return (0, react.createElement)("section", { className: "lks-mapsec" }, (0, react.createElement)("button", {
|
|
4230
|
+
type: "button",
|
|
4231
|
+
className: "lks-signpost",
|
|
4232
|
+
"aria-expanded": String(open),
|
|
4233
|
+
title: open ? tr("rail.section.collapse") : tr("rail.section.expand", { count: section.lessons.length }),
|
|
4234
|
+
onClick: onToggle
|
|
4235
|
+
}, (0, react.createElement)("span", { className: "lks-signpost-num" }, String(section.index + 1)), (0, react.createElement)("span", { className: "lks-signpost-title" }, section.title), (0, react.createElement)("span", { className: "lks-signpost-caret" }, open ? "▾" : "▸")), open && lessons.length > 0 ? (0, react.createElement)("div", {
|
|
4236
|
+
ref: pathRef,
|
|
4237
|
+
className: "lks-mapfield",
|
|
4238
|
+
style: { minHeight: `${String(layout.height)}px` }
|
|
4239
|
+
}, (0, react.createElement)("svg", {
|
|
4240
|
+
className: "lks-mapropes",
|
|
4241
|
+
"aria-hidden": "true",
|
|
4242
|
+
style: { height: `${String(layout.height)}px` }
|
|
4243
|
+
}, ...layout.segments.map((seg) => {
|
|
4244
|
+
const passed = ropePassed(lessons[seg.index] ?? { status: "locked" });
|
|
4245
|
+
return (0, react.createElement)("path", {
|
|
4246
|
+
key: seg.index,
|
|
4247
|
+
d: balloonSegmentToPath(seg),
|
|
4248
|
+
stroke: passed ? "var(--brand)" : "var(--ink-faint)",
|
|
4249
|
+
"stroke-width": passed ? "4" : "2.5",
|
|
4250
|
+
"stroke-opacity": passed ? "0.6" : "0.5",
|
|
4251
|
+
fill: "none",
|
|
4252
|
+
"stroke-linecap": "round",
|
|
4253
|
+
"stroke-dasharray": passed ? "none" : "3 7",
|
|
4254
|
+
style: passed ? {
|
|
4255
|
+
pathLength: 1,
|
|
4256
|
+
animation: "lks-path-draw 600ms var(--ease-out-expo)"
|
|
4257
|
+
} : void 0
|
|
4258
|
+
});
|
|
4259
|
+
})), ...lessons.map((lesson, i) => {
|
|
4260
|
+
const node = layout.nodes[i];
|
|
4261
|
+
if (node === void 0) return null;
|
|
4262
|
+
const h = hashStr(lesson.id);
|
|
4263
|
+
const locked = lesson.status === "locked" || lesson.kind === "exam" && !examAllowed;
|
|
4264
|
+
const showRing = lesson.kind !== "exam" && (lesson.status === "in_progress" || lesson.status === "mastered");
|
|
4265
|
+
const ringPct = lesson.status === "mastered" ? 100 : lesson.status === "in_progress" ? lesson.masteryPct ?? 0 : 0;
|
|
4266
|
+
return (0, react.createElement)("div", {
|
|
4267
|
+
key: lesson.id,
|
|
4268
|
+
className: "lks-mapnode",
|
|
4269
|
+
style: {
|
|
4270
|
+
left: `${String(node.x - 55)}px`,
|
|
4271
|
+
top: `${String(node.y - 38 + 10)}px`,
|
|
4272
|
+
"--bob-delay": `${String(h % 40 / 10)}s`,
|
|
4273
|
+
"--bob-duration": `${String(5 + h % 30 / 10)}s`
|
|
4274
|
+
}
|
|
4275
|
+
}, (0, react.createElement)("button", {
|
|
4276
|
+
type: "button",
|
|
4277
|
+
className: `${bubbleClasses(lesson, examAllowed)}${lesson.focus ? " selected" : ""}`,
|
|
4278
|
+
"aria-disabled": locked || void 0,
|
|
4279
|
+
title: `${lesson.title} — ${lesson.kind === "exam" && !examAllowed ? tr("map.exam.locked") : locked ? tr("map.node.locked") : lesson.due ? tr("map.node.due") : lesson.title}`,
|
|
4280
|
+
onClick: () => {
|
|
4281
|
+
if (!locked) onJump(lesson.id);
|
|
4282
|
+
}
|
|
4283
|
+
}, showRing ? (0, react.createElement)("svg", {
|
|
4284
|
+
className: "lks-bubble-ring",
|
|
4285
|
+
viewBox: "0 0 56 56",
|
|
4286
|
+
"aria-hidden": "true"
|
|
4287
|
+
}, (0, react.createElement)("circle", {
|
|
4288
|
+
cx: 28,
|
|
4289
|
+
cy: 28,
|
|
4290
|
+
r: 25,
|
|
4291
|
+
fill: "none",
|
|
4292
|
+
stroke: "rgb(255 255 255 / 0.2)",
|
|
4293
|
+
"stroke-width": 2.5
|
|
4294
|
+
}), (0, react.createElement)("circle", {
|
|
4295
|
+
cx: 28,
|
|
4296
|
+
cy: 28,
|
|
4297
|
+
r: 25,
|
|
4298
|
+
fill: "none",
|
|
4299
|
+
stroke: lesson.status === "mastered" ? "#ffc800" : "white",
|
|
4300
|
+
"stroke-width": 2.5,
|
|
4301
|
+
"stroke-linecap": "round",
|
|
4302
|
+
"stroke-dasharray": `${String(ringPct / 100 * 157)} 157`
|
|
4303
|
+
})) : null, lesson.kind === "exam" ? locked ? (0, react.createElement)(IconLockFill16, {
|
|
4304
|
+
size: 22,
|
|
4305
|
+
className: "lks-bubble-glyph dim"
|
|
4306
|
+
}) : (0, react.createElement)(IconGoalOutline16, {
|
|
4307
|
+
size: 24,
|
|
4308
|
+
className: "lks-bubble-glyph"
|
|
4309
|
+
}) : lesson.status === "locked" ? (0, react.createElement)(IconLockFill16, {
|
|
4310
|
+
size: 20,
|
|
4311
|
+
className: "lks-bubble-glyph dim"
|
|
4312
|
+
}) : lesson.status === "mastered" ? (0, react.createElement)(IconCrownFill16, {
|
|
4313
|
+
size: 24,
|
|
4314
|
+
className: "lks-bubble-glyph"
|
|
4315
|
+
}) : lesson.status === "in_progress" ? (0, react.createElement)(IconBookFill16, {
|
|
4316
|
+
size: 24,
|
|
4317
|
+
className: "lks-bubble-glyph"
|
|
4318
|
+
}) : (0, react.createElement)(IconStarFill16, {
|
|
4319
|
+
size: 24,
|
|
4320
|
+
className: "lks-bubble-glyph"
|
|
4321
|
+
}), lesson.due && !locked && lesson.kind !== "exam" ? (0, react.createElement)("span", {
|
|
4322
|
+
className: "lks-bubble-due",
|
|
4323
|
+
"aria-label": tr("map.node.due")
|
|
4324
|
+
}, "!") : null), lesson.focus ? (0, react.createElement)("div", { className: "lks-bubble-name" }, lesson.title) : null);
|
|
4325
|
+
})) : null);
|
|
4326
|
+
}
|
|
4327
|
+
//#endregion
|
|
3595
4328
|
//#region src/client/panel.tsx
|
|
3596
4329
|
/**
|
|
3597
4330
|
* The study panel — the sidebar-entry surface that takes over the center
|
|
@@ -3630,13 +4363,6 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3630
4363
|
["record", "zone.record"],
|
|
3631
4364
|
["practice", "zone.practice"]
|
|
3632
4365
|
];
|
|
3633
|
-
function statusIcon(kind, status) {
|
|
3634
|
-
if (kind === "exam") return (0, react.createElement)(IconGoalOutline16, { size: 14 });
|
|
3635
|
-
if (status === "mastered") return (0, react.createElement)(IconCrownFill16, { size: 14 });
|
|
3636
|
-
if (status === "in_progress") return (0, react.createElement)(IconBookFill16, { size: 14 });
|
|
3637
|
-
if (status === "available") return (0, react.createElement)(IconStarFill16, { size: 14 });
|
|
3638
|
-
return (0, react.createElement)(IconLockFill16, { size: 14 });
|
|
3639
|
-
}
|
|
3640
4366
|
/** Toast severity glyph (P6): the state color rides the icon, text stays ink. */
|
|
3641
4367
|
function toastIcon(severity) {
|
|
3642
4368
|
if (severity === "success") return (0, react.createElement)(IconStarFill16, {
|
|
@@ -3855,6 +4581,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3855
4581
|
};
|
|
3856
4582
|
const companion = useCompanionMood();
|
|
3857
4583
|
const setCompanionEvent = companion.emit;
|
|
4584
|
+
const progress = data?.progress ?? null;
|
|
3858
4585
|
const body = (0, react.createElement)("div", {
|
|
3859
4586
|
className: "lks14-body",
|
|
3860
4587
|
"data-pane": narrowPane
|
|
@@ -3865,7 +4592,16 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3865
4592
|
searchLessons,
|
|
3866
4593
|
deleteCourse,
|
|
3867
4594
|
send
|
|
3868
|
-
}), (0, react.createElement)(
|
|
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", {
|
|
4596
|
+
className: "lks-hdr-xp",
|
|
4597
|
+
title: tr("header.xp", { xp: progress?.totalXp ?? 0 })
|
|
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", {
|
|
4599
|
+
className: "lks-hdr-stat lks-hdr-streak",
|
|
4600
|
+
title: tr("header.streak")
|
|
4601
|
+
}, (0, react.createElement)("span", { className: "lks-hdr-glyph" }, (0, react.createElement)(IconFlameFill16, { size: 14 })), String(progress?.streak ?? 0)), (0, react.createElement)("span", {
|
|
4602
|
+
className: "lks-hdr-stat",
|
|
4603
|
+
title: tr("header.level")
|
|
4604
|
+
}, `Lv${String(progress?.level ?? 1)}`)), (0, react.createElement)("div", { className: "lks14-row" }, (0, react.createElement)(ChatPane, {
|
|
3869
4605
|
data,
|
|
3870
4606
|
lesson,
|
|
3871
4607
|
rows,
|
|
@@ -3884,9 +4620,9 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3884
4620
|
deleteNote,
|
|
3885
4621
|
send,
|
|
3886
4622
|
companionEvent: setCompanionEvent
|
|
3887
|
-
}));
|
|
4623
|
+
}))));
|
|
3888
4624
|
return (0, react.createElement)("div", {
|
|
3889
|
-
className: "lks14",
|
|
4625
|
+
className: "lks14 lks-ui",
|
|
3890
4626
|
"data-lks-panel": ""
|
|
3891
4627
|
}, (0, react.createElement)(Companion, {
|
|
3892
4628
|
mood: companion.mood,
|
|
@@ -3947,7 +4683,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3947
4683
|
onClick: () => {
|
|
3948
4684
|
toastStore.startExit(t.id);
|
|
3949
4685
|
}
|
|
3950
|
-
},
|
|
4686
|
+
}, (0, react.createElement)(IconCloseFill16, { size: 12 })))));
|
|
3951
4687
|
}
|
|
3952
4688
|
/** Module-level shell handle so the panel can suppress hand-back on internal opens. */
|
|
3953
4689
|
const PANEL_SHELL = { current: null };
|
|
@@ -3967,8 +4703,10 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3967
4703
|
}));
|
|
3968
4704
|
};
|
|
3969
4705
|
const [confirmDelete, setConfirmDelete] = (0, react.useState)(false);
|
|
3970
|
-
const [showImport, setShowImport] = (0, react.useState)(false);
|
|
3971
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);
|
|
3972
4710
|
const reportError = (err) => {
|
|
3973
4711
|
setError(err instanceof Error ? err.message : String(err));
|
|
3974
4712
|
};
|
|
@@ -4017,134 +4755,162 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4017
4755
|
data,
|
|
4018
4756
|
searchLessons
|
|
4019
4757
|
]);
|
|
4020
|
-
const
|
|
4021
|
-
|
|
4022
|
-
|
|
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),
|
|
4023
4764
|
onClick: () => {
|
|
4024
|
-
|
|
4765
|
+
setPanel(tab);
|
|
4025
4766
|
}
|
|
4026
|
-
}, (0, react.createElement)(
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
if (!confirmDelete) {
|
|
4048
|
-
setConfirmDelete(true);
|
|
4049
|
-
return;
|
|
4050
|
-
}
|
|
4051
|
-
setConfirmDelete(false);
|
|
4052
|
-
deleteCourse(courseId).then(() => {
|
|
4053
|
-
setSelectedCourse("");
|
|
4054
|
-
}, 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;
|
|
4055
4788
|
}
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
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
|
|
4069
4821
|
},
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
if (first !== void 0) {
|
|
4078
|
-
setQuery("");
|
|
4079
|
-
setSearchRows([]);
|
|
4080
|
-
setFocus(first.lessonId).catch(reportError);
|
|
4081
|
-
}
|
|
4822
|
+
examAllowed,
|
|
4823
|
+
open,
|
|
4824
|
+
onToggle: () => {
|
|
4825
|
+
toggleSection(section.title, !open);
|
|
4826
|
+
},
|
|
4827
|
+
onJump: (id) => {
|
|
4828
|
+
setFocus(id).catch(reportError);
|
|
4082
4829
|
}
|
|
4083
|
-
})
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
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") {
|
|
4088
4859
|
setQuery("");
|
|
4089
4860
|
setSearchRows([]);
|
|
4090
|
-
|
|
4861
|
+
setSearchOpen(false);
|
|
4091
4862
|
}
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
}, (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", {
|
|
4100
|
-
className: "lks-btn ghost",
|
|
4101
|
-
style: { marginTop: "6px" },
|
|
4102
|
-
onClick: () => {
|
|
4103
|
-
send(tr("prompt.review"));
|
|
4104
|
-
}
|
|
4105
|
-
}, tr("rail.due.start"))) : null, ...course.sections.flatMap((section) => {
|
|
4106
|
-
const examAllowed = examOpen(section.lessons);
|
|
4107
|
-
const lessons = section.lessons.filter((l) => query.trim() === "" || titleMatches(l.title, query) || l.focus);
|
|
4108
|
-
if (lessons.length === 0) return [];
|
|
4109
|
-
const open = query.trim() !== "" || effectiveOpen(section.title, sectionDefaultOpen(section), sectionOverrides);
|
|
4110
|
-
return [(0, react.createElement)("button", {
|
|
4111
|
-
key: section.title,
|
|
4112
|
-
type: "button",
|
|
4113
|
-
className: "lks14-sechead",
|
|
4114
|
-
"aria-expanded": String(open),
|
|
4115
|
-
title: open ? tr("rail.section.collapse") : tr("rail.section.expand", { count: section.lessons.length }),
|
|
4116
|
-
onClick: () => {
|
|
4117
|
-
toggleSection(section.title, !open);
|
|
4118
|
-
}
|
|
4119
|
-
}, (0, react.createElement)("span", { className: "lks14-secnum" }, String(section.index + 1)), (0, react.createElement)("span", { className: "lks14-secheadt" }, section.title), (0, react.createElement)("span", { className: "lks14-secheadc" }, open ? "▾" : "▸")), ...open ? lessons.map((lesson) => {
|
|
4120
|
-
const locked = lesson.status === "locked" || lesson.kind === "exam" && !examAllowed;
|
|
4121
|
-
return (0, react.createElement)("button", {
|
|
4122
|
-
key: lesson.id,
|
|
4123
|
-
type: "button",
|
|
4124
|
-
className: `lks14-node${lesson.focus ? " focus" : ""}`,
|
|
4125
|
-
"aria-disabled": locked || void 0,
|
|
4126
|
-
title: `${lesson.title} — ${statusTitle(lesson.kind, locked ? "locked" : lesson.status)}`,
|
|
4127
|
-
onClick: () => {
|
|
4128
|
-
if (locked) return;
|
|
4129
|
-
setFocus(lesson.id).catch(reportError);
|
|
4130
|
-
}
|
|
4131
|
-
}, (0, react.createElement)("span", { className: "lks14-g" }, statusIcon(lesson.kind, locked ? "locked" : lesson.status)), (0, react.createElement)("span", { className: "lks14-t" }, lesson.title), lesson.weakConcepts > 0 ? (0, react.createElement)("span", {
|
|
4132
|
-
className: "lks14-tag weak",
|
|
4133
|
-
title: tr("tag.weak", { count: lesson.weakConcepts })
|
|
4134
|
-
}, (0, react.createElement)(IconBoltFill16, { size: 10 }), String(lesson.weakConcepts)) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", {
|
|
4135
|
-
className: "lks14-bar",
|
|
4136
|
-
title: tr("tag.mastery", { pct: lesson.masteryPct })
|
|
4137
|
-
}, (0, react.createElement)("i", { style: { transform: `scaleX(${lesson.masteryPct / 100})` } })) : null, lesson.masteryPct !== null ? (0, react.createElement)("span", { className: "lks14-pct" }, `${lesson.masteryPct}%`) : null);
|
|
4138
|
-
}) : []];
|
|
4139
|
-
}), (0, react.createElement)("button", {
|
|
4140
|
-
className: "lks-btn ghost",
|
|
4141
|
-
style: { marginTop: "10px" },
|
|
4142
|
-
onClick: () => {
|
|
4143
|
-
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);
|
|
4144
4870
|
}
|
|
4145
|
-
}
|
|
4146
|
-
})()
|
|
4147
|
-
|
|
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));
|
|
4148
4914
|
}
|
|
4149
4915
|
function ImportRow({ send }) {
|
|
4150
4916
|
const [url, setUrl] = (0, react.useState)("");
|
|
@@ -4210,23 +4976,17 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4210
4976
|
onClick: () => {
|
|
4211
4977
|
send(tr("proposal.decline.msg", { lesson: proposal.lessonTitle }));
|
|
4212
4978
|
}
|
|
4213
|
-
}, tr("proposal.decline"))) : null, (0, react.createElement)("div", { className: "lks14-
|
|
4214
|
-
key: mode.id,
|
|
4215
|
-
className: `lks14-pill${data?.mode === mode.id ? " on" : ""}`,
|
|
4216
|
-
"aria-pressed": String(data?.mode === mode.id),
|
|
4217
|
-
"aria-disabled": dormant || void 0,
|
|
4218
|
-
title: dormant ? tr("tutor.dormant") : tr(mode.hintKey),
|
|
4219
|
-
onClick: () => {
|
|
4220
|
-
if (!dormant) fire(setMode(mode.id));
|
|
4221
|
-
}
|
|
4222
|
-
}, 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", {
|
|
4223
4980
|
className: "lks14-stream",
|
|
4224
4981
|
ref: streamEl,
|
|
4225
4982
|
"data-lks-feed": rows.length > 0 ? `rows:${String(rows.length)}` : bound ? feedAttached ? "attached-empty" : "waiting" : "no-thread"
|
|
4226
|
-
}, rows.length === 0 ? (0, react.createElement)("div", { className: "lks14-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
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, {
|
|
4230
4990
|
key: a.id,
|
|
4231
4991
|
lessonId: lesson.lessonId,
|
|
4232
4992
|
artifactId: a.id,
|
|
@@ -4240,7 +5000,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4240
5000
|
key: a.id,
|
|
4241
5001
|
artifact: a,
|
|
4242
5002
|
send
|
|
4243
|
-
})), (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", {
|
|
4244
5004
|
key: s.label,
|
|
4245
5005
|
className: "lks14-starter",
|
|
4246
5006
|
"aria-disabled": dormant || void 0,
|
|
@@ -4248,7 +5008,16 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4248
5008
|
onClick: () => {
|
|
4249
5009
|
if (!dormant) send(s.message);
|
|
4250
5010
|
}
|
|
4251
|
-
}, s.label))), (0, react.createElement)("div", { className: "lks14-composer" }, (0, react.createElement)("
|
|
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", {
|
|
4252
5021
|
className: "lks14-composertext",
|
|
4253
5022
|
placeholder: busy ? tr("composer.busy") : tr("tutor.empty.hint"),
|
|
4254
5023
|
value: draft,
|
|
@@ -4259,15 +5028,16 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4259
5028
|
},
|
|
4260
5029
|
onKeyDown: onComposerKey
|
|
4261
5030
|
}), (0, react.createElement)("button", {
|
|
4262
|
-
className: "lks-btn
|
|
5031
|
+
className: "lks-btn-send",
|
|
5032
|
+
"aria-label": tr("composer.send"),
|
|
4263
5033
|
disabled: busy || dormant || draft.trim() === "",
|
|
4264
5034
|
onClick: () => {
|
|
4265
5035
|
send(draft);
|
|
4266
5036
|
}
|
|
4267
5037
|
}, busy ? (0, react.createElement)(IconLoadingOutline16, {
|
|
4268
|
-
size:
|
|
5038
|
+
size: 16,
|
|
4269
5039
|
className: "lks-spin"
|
|
4270
|
-
}) :
|
|
5040
|
+
}) : (0, react.createElement)(IconArrowUpFill16, { size: 18 }))), sendError !== null || error !== null ? (0, react.createElement)("div", { className: "lks-propcard-err" }, sendError ?? error) : null));
|
|
4271
5041
|
}
|
|
4272
5042
|
/** 右栏:the notebook — 讲解/概念图/笔记 tabs (upstream NotebookPanel arrangement). */
|
|
4273
5043
|
function NotebookPane({ data, deleteNote, send, companionEvent }) {
|
|
@@ -4497,7 +5267,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4497
5267
|
setEnhanceTick(Date.now());
|
|
4498
5268
|
});
|
|
4499
5269
|
}, [tab, lesson?.html]);
|
|
4500
|
-
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-
|
|
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", {
|
|
4501
5271
|
className: `lks14-viewtab${tab === "teach" ? " on" : ""}`,
|
|
4502
5272
|
"aria-pressed": String(tab === "teach"),
|
|
4503
5273
|
onClick: () => {
|
|
@@ -4520,7 +5290,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4520
5290
|
setUnseenTick(Date.now());
|
|
4521
5291
|
}
|
|
4522
5292
|
}
|
|
4523
|
-
}, 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", {
|
|
4524
5294
|
className: "lks-ratecard",
|
|
4525
5295
|
"data-lks-rate": lesson.lessonId
|
|
4526
5296
|
}, (0, react.createElement)("div", { className: "lks-ratecard-title" }, tr("review.rate.title")), (0, react.createElement)("div", { className: "lks-ratecard-opts" }, ...[
|
|
@@ -4638,7 +5408,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4638
5408
|
className: "lks-note-text",
|
|
4639
5409
|
dangerouslySetInnerHTML: { __html: renderMarkdown(n.text) }
|
|
4640
5410
|
}), n.quote !== null ? (0, react.createElement)("div", { className: "lks-note-q" }, `“${n.quote}”`) : null))))));
|
|
4641
|
-
return (0, react.createElement)("div", { className: "lks14-col lks14-note" },
|
|
5411
|
+
return (0, react.createElement)("div", { className: "lks14-col lks14-note" }, body, (0, react.createElement)("div", { className: "lks-propcard-err" }, error));
|
|
4642
5412
|
}
|
|
4643
5413
|
//#endregion
|
|
4644
5414
|
//#region src/client/shell-entry.ts
|