dsh-plugin-lookatstudy 0.15.0 → 0.16.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 +586 -79
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +8 -5
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -18,8 +18,299 @@ 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
|
+
//#endregion
|
|
21
306
|
//#region src/client/styles.ts
|
|
22
307
|
/**
|
|
308
|
+
* The study stylesheet. The center-takeover visibility follows the stardeck
|
|
309
|
+
* panel doctrine: the shell view is display:none until the `<html>` active
|
|
310
|
+
* attribute flips, and the host conversation column's own children are hidden
|
|
311
|
+
* behind `!important` so React never fights the takeover.
|
|
312
|
+
*/
|
|
313
|
+
/**
|
|
23
314
|
* Style injection for the study surfaces: one `<style>` element carrying the
|
|
24
315
|
* `.lks14-*` panel classes (the sidebar-entry center-takeover, arranged like
|
|
25
316
|
* upstream LookatStudy) plus the shared `.lks-*` classes the host-native
|
|
@@ -33,12 +324,6 @@ window.__ModuleLoader__.load({
|
|
|
33
324
|
*/
|
|
34
325
|
/** Marker attribute proving the stylesheet is already in the document. */
|
|
35
326
|
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
327
|
const STUDY_CSS = `
|
|
43
328
|
/* State inks: small colored text must clear WCAG AA (4.5:1), but the host's
|
|
44
329
|
raw state tokens (success/warn primaries) sit at 2.3–2.6:1 on light
|
|
@@ -74,6 +359,8 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
74
359
|
.lks14{position:relative;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);overflow:hidden;font-family:var(--dsw-font-family)}
|
|
75
360
|
.lks14 button{font-family:var(--dsw-font-family);cursor:pointer}
|
|
76
361
|
.lks14-body{display:flex;flex:1;min-width:0;min-height:0}
|
|
362
|
+
.lks14-righthalf{flex:1;min-width:0;display:flex;flex-direction:column;min-height:0}
|
|
363
|
+
.lks14-row{flex:1;min-height:0;display:flex}
|
|
77
364
|
.lks14-col{display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden}
|
|
78
365
|
.lks14-colhead{flex:none;display:flex;align-items:center;gap:8px;padding:10px 12px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
79
366
|
|
|
@@ -90,22 +377,6 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
90
377
|
.lks14-duebox{border:1px solid var(--dsw-alias-border-l1);border-radius:10px;padding:8px 10px;margin:8px 0;font-size:12.5px}
|
|
91
378
|
.lks14-dueitem{display:flex;justify-content:space-between;gap:6px;padding:2px 0;color:var(--dsw-alias-label-secondary)}
|
|
92
379
|
.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
380
|
.lks14-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;padding:18px 6px;line-height:1.7}
|
|
110
381
|
.lks14-import{margin:8px 0}
|
|
111
382
|
.lks14-inputrow{display:flex;gap:6px}
|
|
@@ -220,7 +491,7 @@ html[data-dsh-lookatstudy-active] [class*='centerCol'] > :not([data-dsh-lookatst
|
|
|
220
491
|
.lks14-rail{flex:none;max-height:32vh;border-right:none;border-bottom:1px solid var(--dsw-alias-border-l1)}
|
|
221
492
|
.lks14-chat{min-width:0}
|
|
222
493
|
.lks14-note{flex:1 1 auto;min-height:0}
|
|
223
|
-
.lks14-body[data-pane='rail'] .lks14-
|
|
494
|
+
.lks14-body[data-pane='rail'] .lks14-righthalf{display:none}
|
|
224
495
|
.lks14-body[data-pane='chat'] .lks14-rail,.lks14-body[data-pane='chat'] .lks14-note,
|
|
225
496
|
.lks14-body[data-pane='note'] .lks14-rail,.lks14-body[data-pane='note'] .lks14-chat{display:none}
|
|
226
497
|
.lks14-body[data-pane='rail'] .lks14-rail{display:flex;max-height:none;flex:1 1 auto}
|
|
@@ -394,7 +665,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
394
665
|
if (existing !== null) return existing;
|
|
395
666
|
const style = document.createElement("style");
|
|
396
667
|
style.setAttribute(STYLE_ID, "");
|
|
397
|
-
style.textContent = STUDY_CSS;
|
|
668
|
+
style.textContent = STUDY_CSS + UPSTREAM_CSS;
|
|
398
669
|
document.head.appendChild(style);
|
|
399
670
|
return style;
|
|
400
671
|
}
|
|
@@ -1656,8 +1927,8 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1656
1927
|
"col.tutor": "导师",
|
|
1657
1928
|
"col.bb": "黑板",
|
|
1658
1929
|
"pane.rail": "课程",
|
|
1659
|
-
"pane.
|
|
1660
|
-
"pane.
|
|
1930
|
+
"pane.chat": "对话",
|
|
1931
|
+
"pane.note": "黑板",
|
|
1661
1932
|
"viewtab.teach": "讲解",
|
|
1662
1933
|
"viewtab.cmap": "概念图",
|
|
1663
1934
|
"viewtab.cmap.title": "本课概念之间的关系图",
|
|
@@ -1724,9 +1995,12 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1724
1995
|
"proposal.accept.msg": "我接受《{lesson}》的掌握度提议,帮我标记为掌握。",
|
|
1725
1996
|
"proposal.decline.msg": "《{lesson}》我先再练练,暂不接受提前毕业。",
|
|
1726
1997
|
"tutor.thinking": "导师思考中…",
|
|
1727
|
-
"
|
|
1728
|
-
"
|
|
1729
|
-
"
|
|
1998
|
+
"header.xp": "经验值 {xp}",
|
|
1999
|
+
"header.streak": "连续学习天数",
|
|
2000
|
+
"header.level": "等级",
|
|
2001
|
+
"map.node.locked": "锁定 — 先完成前面的课时",
|
|
2002
|
+
"map.node.due": "到了复习时间",
|
|
2003
|
+
"map.exam.locked": "考试未解锁 — 本章节全部课时掌握度 ≥50% 后开放",
|
|
1730
2004
|
"companion.poke": "戳一戳伴学伙伴",
|
|
1731
2005
|
"companion.poked": "伴学伙伴向你眨了眨眼",
|
|
1732
2006
|
"companion.form.ember": "小焰",
|
|
@@ -1856,8 +2130,8 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1856
2130
|
"col.tutor": "Tutor",
|
|
1857
2131
|
"col.bb": "Blackboard",
|
|
1858
2132
|
"pane.rail": "Courses",
|
|
1859
|
-
"pane.
|
|
1860
|
-
"pane.
|
|
2133
|
+
"pane.chat": "Chat",
|
|
2134
|
+
"pane.note": "Board",
|
|
1861
2135
|
"viewtab.teach": "Teach",
|
|
1862
2136
|
"viewtab.cmap": "Concepts",
|
|
1863
2137
|
"viewtab.cmap.title": "The lesson's concepts relate",
|
|
@@ -1924,9 +2198,12 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
1924
2198
|
"proposal.accept.msg": "I accept the mastery proposal for \"{lesson}\" — mark it mastered.",
|
|
1925
2199
|
"proposal.decline.msg": "I want more practice on \"{lesson}\" before graduating.",
|
|
1926
2200
|
"tutor.thinking": "The tutor is thinking…",
|
|
1927
|
-
"
|
|
1928
|
-
"
|
|
1929
|
-
"
|
|
2201
|
+
"header.xp": "{xp} XP",
|
|
2202
|
+
"header.streak": "Day streak",
|
|
2203
|
+
"header.level": "Level",
|
|
2204
|
+
"map.node.locked": "Locked — finish the previous lessons first",
|
|
2205
|
+
"map.node.due": "Due for review",
|
|
2206
|
+
"map.exam.locked": "Exam locked — every lesson in this section needs ≥50% mastery",
|
|
1930
2207
|
"companion.poke": "Poke the companion",
|
|
1931
2208
|
"companion.poked": "The companion winks at you",
|
|
1932
2209
|
"companion.form.ember": "Ember",
|
|
@@ -3592,6 +3869,248 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3592
3869
|
return section.lessons.some((l) => l.focus || l.kind !== "exam" && l.status !== "mastered" && l.status !== "locked");
|
|
3593
3870
|
}
|
|
3594
3871
|
//#endregion
|
|
3872
|
+
//#region src/vendor/map-layout.ts
|
|
3873
|
+
/** 容器宽度(章节内可用宽度,扣除 padding)。MapRail 章节容器 px-2。 */
|
|
3874
|
+
const CONTAINER_WIDTH = 268;
|
|
3875
|
+
const NODE_RADIUS = 28;
|
|
3876
|
+
const NODE_SPACING_Y = 104;
|
|
3877
|
+
const BALLOON_MARGIN = 18;
|
|
3878
|
+
/** 首节点中心下限(防顶撞上方章节路牌)。MapRail 渲染偏移 +12 + NODE_H/2=38 → wrapper 顶 = y-26 ≥ 8。 */
|
|
3879
|
+
const TOP_PAD = 34;
|
|
3880
|
+
/** y 轴抖动幅度(px)。贪心兜底后,jitter 不再是重叠源,仅负责高低参差。 */
|
|
3881
|
+
const Y_JITTER = 26;
|
|
3882
|
+
/** 绳子下垂量(px,模拟重力)。让贝塞尔像被拽下来的绳子,而非 S 形箭头。 */
|
|
3883
|
+
const ROPE_SAG = 22;
|
|
3884
|
+
/**
|
|
3885
|
+
* 确定性字符串哈希(FNV-1a 变体,32-bit)。
|
|
3886
|
+
* 同输入永远同输出 → 同一节点 id 每次渲染算出的抖动相同 → 气球位置稳定不跳。
|
|
3887
|
+
* 用于气球横向抖动 + bob 相位 + 星点位置 + 天体色切。
|
|
3888
|
+
*/
|
|
3889
|
+
function hashStr(s) {
|
|
3890
|
+
let h = 2166136261;
|
|
3891
|
+
for (let i = 0; i < s.length; i++) {
|
|
3892
|
+
h ^= s.charCodeAt(i);
|
|
3893
|
+
h = Math.imul(h, 16777619);
|
|
3894
|
+
}
|
|
3895
|
+
h ^= h >>> 16;
|
|
3896
|
+
h = Math.imul(h, 2246822507);
|
|
3897
|
+
h ^= h >>> 13;
|
|
3898
|
+
h = Math.imul(h, 3266489909);
|
|
3899
|
+
h ^= h >>> 16;
|
|
3900
|
+
return h >>> 0;
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* 算某 section 的气球节点坐标 + 绳子连接路径。
|
|
3904
|
+
* 容器高度由 nodes 数量决定(调用方据此设容器 min-height)。
|
|
3905
|
+
*
|
|
3906
|
+
* 抖动算法:v0.6 同时在 x 和 y 两轴抖动 → 气球真正"飘"在空中,
|
|
3907
|
+
* 不再是呆板的直线/微弯列。x 大幅抖动(用满容器宽度),y 在基线上下 ±Y_JITTER。
|
|
3908
|
+
* 两个独立哈希(x 用 seed:i,y 用 seed:i:y)→ 抖动方向不相关,分布更自然。
|
|
3909
|
+
*
|
|
3910
|
+
* v0.8 防重叠:抖动后跑一趟**贪心向下推**(确定性,只增 y,保持单调),
|
|
3911
|
+
* 1) 首节点中心不低于 TOP_PAD —— 防顶撞上方章节路牌;
|
|
3912
|
+
* 2) 相邻节点中心纵向距 ≥ MIN_GAP_Y(= 盒高 + 8px 呼吸)—— 视觉盒永不重叠。
|
|
3913
|
+
* 任何 seed / 任何课数都保证不重叠(verify-map-layout T11 跨 240 种子穷举验证)。
|
|
3914
|
+
*/
|
|
3915
|
+
function computeBalloonLayout(lessonCount, containerWidth = CONTAINER_WIDTH, seed = "") {
|
|
3916
|
+
const nodes = [];
|
|
3917
|
+
const centerX = containerWidth / 2;
|
|
3918
|
+
const maxDrift = containerWidth / 2 - BALLOON_MARGIN - NODE_RADIUS;
|
|
3919
|
+
for (let i = 0; i < lessonCount; i++) {
|
|
3920
|
+
const baseY = TOP_PAD + i * NODE_SPACING_Y;
|
|
3921
|
+
const x = centerX + (hashStr(`${seed}:${i}`) / 4294967295 * 2 - 1) * maxDrift;
|
|
3922
|
+
const y = baseY + (hashStr(`${seed}:${i}:y`) / 4294967295 * 2 - 1) * Y_JITTER;
|
|
3923
|
+
nodes.push({
|
|
3924
|
+
x,
|
|
3925
|
+
y,
|
|
3926
|
+
index: i
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
if (nodes.length > 0) {
|
|
3930
|
+
if (nodes[0].y < TOP_PAD) nodes[0].y = TOP_PAD;
|
|
3931
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
3932
|
+
const minY = nodes[i - 1].y + 84;
|
|
3933
|
+
if (nodes[i].y < minY) nodes[i].y = minY;
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
const segments = [];
|
|
3937
|
+
for (let i = 0; i < nodes.length - 1; i++) segments.push({
|
|
3938
|
+
from: nodes[i],
|
|
3939
|
+
to: nodes[i + 1],
|
|
3940
|
+
index: i
|
|
3941
|
+
});
|
|
3942
|
+
return {
|
|
3943
|
+
nodes,
|
|
3944
|
+
segments,
|
|
3945
|
+
height: nodes.length === 0 ? 40 : nodes[nodes.length - 1].y + 38 + 10
|
|
3946
|
+
};
|
|
3947
|
+
}
|
|
3948
|
+
/**
|
|
3949
|
+
* 两节点间的绳子贝塞尔(下垂)。
|
|
3950
|
+
* 控制点:y 中点 + ROPE_SAG(向下垂,模拟重力);x 用各自端点 x。
|
|
3951
|
+
* 比原 S 形软,像被拽下来的绳子。
|
|
3952
|
+
*/
|
|
3953
|
+
function balloonSegmentToPath(seg) {
|
|
3954
|
+
const { from, to } = seg;
|
|
3955
|
+
const controlY = (from.y + to.y) / 2 + ROPE_SAG;
|
|
3956
|
+
return `M ${from.x} ${from.y} C ${from.x} ${controlY}, ${to.x} ${controlY}, ${to.x} ${to.y}`;
|
|
3957
|
+
}
|
|
3958
|
+
//#endregion
|
|
3959
|
+
//#region src/client/maprail.tsx
|
|
3960
|
+
/**
|
|
3961
|
+
* MapRail balloon map (P9b) — the upstream v0.28 course map ported onto the
|
|
3962
|
+
* plugin's rail: deterministic balloon layout (vendored mapLayout engine),
|
|
3963
|
+
* sagging bezier ropes with path-draw on walked segments, state bubbles with
|
|
3964
|
+
* 3D sphere lighting, signpost section heads, and the sky backdrop. This is
|
|
3965
|
+
* upstream's own reduced-motion presentation (static layout + balloon-bob
|
|
3966
|
+
* CSS), which is exactly the deterministic half of its physics map.
|
|
3967
|
+
*/
|
|
3968
|
+
/** Upstream bubbleClass port: the four study states + the three exam variants.
|
|
3969
|
+
* Status→class goes through a table (in_progress → in-progress, like upstream's switch). */
|
|
3970
|
+
const STATUS_CLASS = {
|
|
3971
|
+
locked: "locked",
|
|
3972
|
+
available: "available",
|
|
3973
|
+
in_progress: "in-progress",
|
|
3974
|
+
mastered: "mastered"
|
|
3975
|
+
};
|
|
3976
|
+
function bubbleClasses(lesson, examAllowed) {
|
|
3977
|
+
if (lesson.kind === "exam") {
|
|
3978
|
+
if (!examAllowed) return "lks-bubble lks-exam-locked";
|
|
3979
|
+
return lesson.status === "mastered" ? "lks-bubble lks-exam-passed" : "lks-bubble lks-exam";
|
|
3980
|
+
}
|
|
3981
|
+
return `lks-bubble lks-bubble-${STATUS_CLASS[lesson.status] ?? "locked"}`;
|
|
3982
|
+
}
|
|
3983
|
+
/** A rope segment is "walked" when its FROM node has been opened
|
|
3984
|
+
* (upstream ropeStyleIsPassed: mastered | in_progress | available). */
|
|
3985
|
+
function ropePassed(from) {
|
|
3986
|
+
return from.status === "mastered" || from.status === "in_progress" || from.status === "available";
|
|
3987
|
+
}
|
|
3988
|
+
/** Deterministic sky preset per course (upstream pickPreset's CSS-essence port). */
|
|
3989
|
+
function pickSky(courseId) {
|
|
3990
|
+
return [
|
|
3991
|
+
"day",
|
|
3992
|
+
"dusk",
|
|
3993
|
+
"night"
|
|
3994
|
+
][hashStr(courseId) % 3];
|
|
3995
|
+
}
|
|
3996
|
+
/** One section: signpost head (still the plugin's collapse toggle) + the balloon field. */
|
|
3997
|
+
function MapSectionView({ section, examAllowed, open, onToggle, onJump }) {
|
|
3998
|
+
const pathRef = (0, react.useRef)(null);
|
|
3999
|
+
const [containerW, setContainerW] = (0, react.useState)(268);
|
|
4000
|
+
(0, react.useEffect)(() => {
|
|
4001
|
+
if (pathRef.current === null) return;
|
|
4002
|
+
const measure = () => {
|
|
4003
|
+
const w = pathRef.current?.clientWidth ?? 268;
|
|
4004
|
+
setContainerW(w > 0 ? w : 268);
|
|
4005
|
+
};
|
|
4006
|
+
measure();
|
|
4007
|
+
const ro = new ResizeObserver(measure);
|
|
4008
|
+
ro.observe(pathRef.current);
|
|
4009
|
+
return () => {
|
|
4010
|
+
ro.disconnect();
|
|
4011
|
+
};
|
|
4012
|
+
}, [open]);
|
|
4013
|
+
const lessons = section.lessons;
|
|
4014
|
+
const layout = computeBalloonLayout(lessons.length, containerW, section.title);
|
|
4015
|
+
return (0, react.createElement)("section", { className: "lks-mapsec" }, (0, react.createElement)("button", {
|
|
4016
|
+
type: "button",
|
|
4017
|
+
className: "lks-signpost",
|
|
4018
|
+
"aria-expanded": String(open),
|
|
4019
|
+
title: open ? tr("rail.section.collapse") : tr("rail.section.expand", { count: section.lessons.length }),
|
|
4020
|
+
onClick: onToggle
|
|
4021
|
+
}, (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", {
|
|
4022
|
+
ref: pathRef,
|
|
4023
|
+
className: "lks-mapfield",
|
|
4024
|
+
style: { minHeight: `${String(layout.height)}px` }
|
|
4025
|
+
}, (0, react.createElement)("svg", {
|
|
4026
|
+
className: "lks-mapropes",
|
|
4027
|
+
"aria-hidden": "true",
|
|
4028
|
+
style: { height: `${String(layout.height)}px` }
|
|
4029
|
+
}, ...layout.segments.map((seg) => {
|
|
4030
|
+
const passed = ropePassed(lessons[seg.index] ?? { status: "locked" });
|
|
4031
|
+
return (0, react.createElement)("path", {
|
|
4032
|
+
key: seg.index,
|
|
4033
|
+
d: balloonSegmentToPath(seg),
|
|
4034
|
+
stroke: passed ? "var(--brand)" : "var(--ink-faint)",
|
|
4035
|
+
"stroke-width": passed ? "4" : "2.5",
|
|
4036
|
+
"stroke-opacity": passed ? "0.6" : "0.5",
|
|
4037
|
+
fill: "none",
|
|
4038
|
+
"stroke-linecap": "round",
|
|
4039
|
+
"stroke-dasharray": passed ? "none" : "3 7",
|
|
4040
|
+
style: passed ? {
|
|
4041
|
+
pathLength: 1,
|
|
4042
|
+
animation: "lks-path-draw 600ms var(--ease-out-expo)"
|
|
4043
|
+
} : void 0
|
|
4044
|
+
});
|
|
4045
|
+
})), ...lessons.map((lesson, i) => {
|
|
4046
|
+
const node = layout.nodes[i];
|
|
4047
|
+
if (node === void 0) return null;
|
|
4048
|
+
const h = hashStr(lesson.id);
|
|
4049
|
+
const locked = lesson.status === "locked" || lesson.kind === "exam" && !examAllowed;
|
|
4050
|
+
const showRing = lesson.kind !== "exam" && (lesson.status === "in_progress" || lesson.status === "mastered");
|
|
4051
|
+
const ringPct = lesson.status === "mastered" ? 100 : lesson.status === "in_progress" ? lesson.masteryPct ?? 0 : 0;
|
|
4052
|
+
return (0, react.createElement)("div", {
|
|
4053
|
+
key: lesson.id,
|
|
4054
|
+
className: "lks-mapnode",
|
|
4055
|
+
style: {
|
|
4056
|
+
left: `${String(node.x - 55)}px`,
|
|
4057
|
+
top: `${String(node.y - 38 + 10)}px`,
|
|
4058
|
+
"--bob-delay": `${String(h % 40 / 10)}s`,
|
|
4059
|
+
"--bob-duration": `${String(5 + h % 30 / 10)}s`
|
|
4060
|
+
}
|
|
4061
|
+
}, (0, react.createElement)("button", {
|
|
4062
|
+
type: "button",
|
|
4063
|
+
className: `${bubbleClasses(lesson, examAllowed)}${lesson.focus ? " selected" : ""}`,
|
|
4064
|
+
"aria-disabled": locked || void 0,
|
|
4065
|
+
title: `${lesson.title} — ${lesson.kind === "exam" && !examAllowed ? tr("map.exam.locked") : locked ? tr("map.node.locked") : lesson.due ? tr("map.node.due") : lesson.title}`,
|
|
4066
|
+
onClick: () => {
|
|
4067
|
+
if (!locked) onJump(lesson.id);
|
|
4068
|
+
}
|
|
4069
|
+
}, showRing ? (0, react.createElement)("svg", {
|
|
4070
|
+
className: "lks-bubble-ring",
|
|
4071
|
+
viewBox: "0 0 56 56",
|
|
4072
|
+
"aria-hidden": "true"
|
|
4073
|
+
}, (0, react.createElement)("circle", {
|
|
4074
|
+
cx: 28,
|
|
4075
|
+
cy: 28,
|
|
4076
|
+
r: 25,
|
|
4077
|
+
fill: "none",
|
|
4078
|
+
stroke: "rgb(255 255 255 / 0.2)",
|
|
4079
|
+
"stroke-width": 2.5
|
|
4080
|
+
}), (0, react.createElement)("circle", {
|
|
4081
|
+
cx: 28,
|
|
4082
|
+
cy: 28,
|
|
4083
|
+
r: 25,
|
|
4084
|
+
fill: "none",
|
|
4085
|
+
stroke: lesson.status === "mastered" ? "#ffc800" : "white",
|
|
4086
|
+
"stroke-width": 2.5,
|
|
4087
|
+
"stroke-linecap": "round",
|
|
4088
|
+
"stroke-dasharray": `${String(ringPct / 100 * 157)} 157`
|
|
4089
|
+
})) : null, lesson.kind === "exam" ? locked ? (0, react.createElement)(IconLockFill16, {
|
|
4090
|
+
size: 22,
|
|
4091
|
+
className: "lks-bubble-glyph dim"
|
|
4092
|
+
}) : (0, react.createElement)(IconGoalOutline16, {
|
|
4093
|
+
size: 24,
|
|
4094
|
+
className: "lks-bubble-glyph"
|
|
4095
|
+
}) : lesson.status === "locked" ? (0, react.createElement)(IconLockFill16, {
|
|
4096
|
+
size: 20,
|
|
4097
|
+
className: "lks-bubble-glyph dim"
|
|
4098
|
+
}) : lesson.status === "mastered" ? (0, react.createElement)(IconCrownFill16, {
|
|
4099
|
+
size: 24,
|
|
4100
|
+
className: "lks-bubble-glyph"
|
|
4101
|
+
}) : lesson.status === "in_progress" ? (0, react.createElement)(IconBookFill16, {
|
|
4102
|
+
size: 24,
|
|
4103
|
+
className: "lks-bubble-glyph"
|
|
4104
|
+
}) : (0, react.createElement)(IconStarFill16, {
|
|
4105
|
+
size: 24,
|
|
4106
|
+
className: "lks-bubble-glyph"
|
|
4107
|
+
}), lesson.due && !locked && lesson.kind !== "exam" ? (0, react.createElement)("span", {
|
|
4108
|
+
className: "lks-bubble-due",
|
|
4109
|
+
"aria-label": tr("map.node.due")
|
|
4110
|
+
}, "!") : null), lesson.focus ? (0, react.createElement)("div", { className: "lks-bubble-name" }, lesson.title) : null);
|
|
4111
|
+
})) : null);
|
|
4112
|
+
}
|
|
4113
|
+
//#endregion
|
|
3595
4114
|
//#region src/client/panel.tsx
|
|
3596
4115
|
/**
|
|
3597
4116
|
* The study panel — the sidebar-entry surface that takes over the center
|
|
@@ -3630,13 +4149,6 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3630
4149
|
["record", "zone.record"],
|
|
3631
4150
|
["practice", "zone.practice"]
|
|
3632
4151
|
];
|
|
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
4152
|
/** Toast severity glyph (P6): the state color rides the icon, text stays ink. */
|
|
3641
4153
|
function toastIcon(severity) {
|
|
3642
4154
|
if (severity === "success") return (0, react.createElement)(IconStarFill16, {
|
|
@@ -3855,6 +4367,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3855
4367
|
};
|
|
3856
4368
|
const companion = useCompanionMood();
|
|
3857
4369
|
const setCompanionEvent = companion.emit;
|
|
4370
|
+
const progress = data?.progress ?? null;
|
|
3858
4371
|
const body = (0, react.createElement)("div", {
|
|
3859
4372
|
className: "lks14-body",
|
|
3860
4373
|
"data-pane": narrowPane
|
|
@@ -3865,7 +4378,16 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3865
4378
|
searchLessons,
|
|
3866
4379
|
deleteCourse,
|
|
3867
4380
|
send
|
|
3868
|
-
}), (0, react.createElement)(
|
|
4381
|
+
}), (0, react.createElement)("div", { className: "lks14-righthalf" }, (0, react.createElement)("div", { className: "lks14-appheader" }, (0, react.createElement)("span", { className: "lks-hdr-title" }, lesson?.courseTitle ?? tr("tab.label")), (0, react.createElement)("span", {
|
|
4382
|
+
className: "lks-hdr-xp",
|
|
4383
|
+
title: tr("header.xp", { xp: progress?.totalXp ?? 0 })
|
|
4384
|
+
}, (0, react.createElement)("span", { className: "lks-hdr-stat" }, "⚡"), (0, react.createElement)("span", { className: "lks-hdr-xpbar" }, (0, react.createElement)("i", { style: { width: `${Math.min(100, progress?.levelPct ?? 0)}%` } }))), (0, react.createElement)("span", {
|
|
4385
|
+
className: "lks-hdr-stat lks-hdr-streak",
|
|
4386
|
+
title: tr("header.streak")
|
|
4387
|
+
}, "🔥", String(progress?.streak ?? 0)), (0, react.createElement)("span", {
|
|
4388
|
+
className: "lks-hdr-stat",
|
|
4389
|
+
title: tr("header.level")
|
|
4390
|
+
}, `Lv${String(progress?.level ?? 1)}`)), (0, react.createElement)("div", { className: "lks14-row" }, (0, react.createElement)(ChatPane, {
|
|
3869
4391
|
data,
|
|
3870
4392
|
lesson,
|
|
3871
4393
|
rows,
|
|
@@ -3884,9 +4406,9 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
3884
4406
|
deleteNote,
|
|
3885
4407
|
send,
|
|
3886
4408
|
companionEvent: setCompanionEvent
|
|
3887
|
-
}));
|
|
4409
|
+
}))));
|
|
3888
4410
|
return (0, react.createElement)("div", {
|
|
3889
|
-
className: "lks14",
|
|
4411
|
+
className: "lks14 lks-ui",
|
|
3890
4412
|
"data-lks-panel": ""
|
|
3891
4413
|
}, (0, react.createElement)(Companion, {
|
|
3892
4414
|
mood: companion.mood,
|
|
@@ -4026,7 +4548,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4026
4548
|
}, (0, react.createElement)(IconDownloadOutline16, null), tr("rail.empty.demo")), (0, react.createElement)(ImportRow, { send })) : (() => {
|
|
4027
4549
|
const courseId = data.courses.some((c) => c.courseId === selectedCourse) ? selectedCourse : data.courses[0].courseId;
|
|
4028
4550
|
const course = data.courses.find((c) => c.courseId === courseId);
|
|
4029
|
-
return (0, react.createElement)("div",
|
|
4551
|
+
return (0, react.createElement)("div", { className: `lks-map lks-sky-${pickSky(courseId)}` }, (0, react.createElement)("div", { className: "lks14-railhead" }, data.courses.length > 1 ? (0, react.createElement)("select", {
|
|
4030
4552
|
className: "lks-set-select",
|
|
4031
4553
|
value: courseId,
|
|
4032
4554
|
onChange: (e) => {
|
|
@@ -4107,35 +4629,22 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4107
4629
|
const lessons = section.lessons.filter((l) => query.trim() === "" || titleMatches(l.title, query) || l.focus);
|
|
4108
4630
|
if (lessons.length === 0) return [];
|
|
4109
4631
|
const open = query.trim() !== "" || effectiveOpen(section.title, sectionDefaultOpen(section), sectionOverrides);
|
|
4110
|
-
return [(0, react.createElement)(
|
|
4632
|
+
return [(0, react.createElement)(MapSectionView, {
|
|
4111
4633
|
key: section.title,
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4634
|
+
section: {
|
|
4635
|
+
title: section.title,
|
|
4636
|
+
index: section.index,
|
|
4637
|
+
lessons
|
|
4638
|
+
},
|
|
4639
|
+
examAllowed,
|
|
4640
|
+
open,
|
|
4641
|
+
onToggle: () => {
|
|
4117
4642
|
toggleSection(section.title, !open);
|
|
4643
|
+
},
|
|
4644
|
+
onJump: (id) => {
|
|
4645
|
+
setFocus(id).catch(reportError);
|
|
4118
4646
|
}
|
|
4119
|
-
}
|
|
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
|
-
}) : []];
|
|
4647
|
+
})];
|
|
4139
4648
|
}), (0, react.createElement)("button", {
|
|
4140
4649
|
className: "lks-btn ghost",
|
|
4141
4650
|
style: { marginTop: "10px" },
|
|
@@ -4223,10 +4732,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4223
4732
|
className: "lks14-stream",
|
|
4224
4733
|
ref: streamEl,
|
|
4225
4734
|
"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-empty" }, dormant ? tr("tutor.dormant") : tr("tutor.empty"), (0, react.createElement)("br"), tr("tutor.empty.hint")) : rows.map((row, i) => chatRow(row, !dormant && i === lastAssistant ? { send } : void 0)), rows.length > 0 && rows[rows.length - 1].role === "user" ? (0, react.createElement)("div", { className: "lks14-thinking" }, (0, react.createElement)(
|
|
4227
|
-
size: 13,
|
|
4228
|
-
className: "lks-spin"
|
|
4229
|
-
}), tr("tutor.thinking")) : null), ...(lesson?.artifacts ?? []).filter((a) => a.artifactType === "quiz").map((a) => (0, react.createElement)(QuizCard, {
|
|
4735
|
+
}, rows.length === 0 ? (0, react.createElement)("div", { className: "lks14-empty" }, dormant ? tr("tutor.dormant") : tr("tutor.empty"), (0, react.createElement)("br"), tr("tutor.empty.hint")) : rows.map((row, i) => chatRow(row, !dormant && i === lastAssistant ? { send } : void 0)), rows.length > 0 && rows[rows.length - 1].role === "user" ? (0, react.createElement)("div", { className: "lks14-thinking" }, (0, react.createElement)("i", null), (0, react.createElement)("i", null), (0, react.createElement)("i", null)) : null), ...(lesson?.artifacts ?? []).filter((a) => a.artifactType === "quiz").map((a) => (0, react.createElement)(QuizCard, {
|
|
4230
4736
|
key: a.id,
|
|
4231
4737
|
lessonId: lesson.lessonId,
|
|
4232
4738
|
artifactId: a.id,
|
|
@@ -4248,7 +4754,7 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4248
4754
|
onClick: () => {
|
|
4249
4755
|
if (!dormant) send(s.message);
|
|
4250
4756
|
}
|
|
4251
|
-
}, s.label))), (0, react.createElement)("div", { className: "lks14-composer" }, (0, react.createElement)("textarea", {
|
|
4757
|
+
}, s.label))), (0, react.createElement)("div", { className: "lks14-composer" }, (0, react.createElement)("div", { className: "lks14-composer-card" }, (0, react.createElement)("textarea", {
|
|
4252
4758
|
className: "lks14-composertext",
|
|
4253
4759
|
placeholder: busy ? tr("composer.busy") : tr("tutor.empty.hint"),
|
|
4254
4760
|
value: draft,
|
|
@@ -4259,15 +4765,16 @@ mark.lks-hl{background:var(--dsw-alias-state-warn-tertiary);color:inherit;border
|
|
|
4259
4765
|
},
|
|
4260
4766
|
onKeyDown: onComposerKey
|
|
4261
4767
|
}), (0, react.createElement)("button", {
|
|
4262
|
-
className: "lks-btn
|
|
4768
|
+
className: "lks-btn-send",
|
|
4769
|
+
"aria-label": tr("composer.send"),
|
|
4263
4770
|
disabled: busy || dormant || draft.trim() === "",
|
|
4264
4771
|
onClick: () => {
|
|
4265
4772
|
send(draft);
|
|
4266
4773
|
}
|
|
4267
4774
|
}, busy ? (0, react.createElement)(IconLoadingOutline16, {
|
|
4268
|
-
size:
|
|
4775
|
+
size: 16,
|
|
4269
4776
|
className: "lks-spin"
|
|
4270
|
-
}) :
|
|
4777
|
+
}) : "↑")), sendError !== null || error !== null ? (0, react.createElement)("div", { className: "lks-propcard-err" }, sendError ?? error) : null));
|
|
4271
4778
|
}
|
|
4272
4779
|
/** 右栏:the notebook — 讲解/概念图/笔记 tabs (upstream NotebookPanel arrangement). */
|
|
4273
4780
|
function NotebookPane({ data, deleteNote, send, companionEvent }) {
|