@lark-apaas/coding-registry-buildless 0.1.10 → 0.1.11-alpha.20260729134521

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.
@@ -14,9 +14,6 @@
14
14
  // ←/→ seek, space, and 0-to-reset controls, and persists the playhead.
15
15
  // Set the optional `poster` prop (seconds) to the moment your opening scene is
16
16
  // fully composed — the product thumbnail freezes on that frame (default ~1s).
17
- // Stage also exposes window.__stage — seek(t) / pause() / play() / time() /
18
- // playing() / duration — the deterministic way to drive the playhead from
19
- // outside (tooling, self-checks); never synthesize key/scrubber events for that.
20
17
  // Inside <Stage>, any child can call useTime() to read the current
21
18
  // playhead (seconds). Or wrap content in <Sprite start={1} end={4}>...</Sprite>
22
19
  // to only render during that window (outside it, children are unmounted from
@@ -439,24 +436,6 @@ function Stage({
439
436
  return () => window.removeEventListener('keydown', onKey);
440
437
  }, [duration]);
441
438
 
442
- // Programmatic driver for tooling (preflight / screenshot-at-t): freezes the
443
- // playhead on an exact frame, unlike synthesized key/scrubber events.
444
- const timeRef = React.useRef(time);
445
- timeRef.current = time;
446
- const playingRef = React.useRef(playing);
447
- playingRef.current = playing;
448
- React.useEffect(() => {
449
- window.__stage = {
450
- duration,
451
- seek: (t) => { setPlaying(false); setTime(clamp(t, 0, duration)); },
452
- pause: () => setPlaying(false),
453
- play: () => setPlaying(true),
454
- time: () => timeRef.current,
455
- playing: () => playingRef.current,
456
- };
457
- return () => { delete window.__stage; };
458
- }, [duration]);
459
-
460
439
  const ctxValue = React.useMemo(
461
440
  () => ({ time, duration, playing, setTime, setPlaying }),
462
441
  [time, duration, playing]
@@ -847,10 +847,6 @@ function DCViewport({ children, minScale = 0.1, maxScale = 8, style = {} }) {
847
847
  // Clear the pan-tick guard so apply() re-posts the current scale
848
848
  // even if it's unchanged — the host just reset dcScale to 1.
849
849
  window.parent.postMessage({ type: 'miaoda:canvas:present' }, '*');
850
- // Re-announce the upgrade capability too — the host's readyGen reset
851
- // (fires on iframe 'load', which for image/font-heavy canvases lands
852
- // after the mount-time announce) drops prior state, so re-declare here.
853
- window.parent.postMessage({ type: 'miaoda:upgrade:available', kind: 'design-canvas' }, '*');
854
850
  lastPostedScale.current = undefined;
855
851
  apply();
856
852
  }
@@ -864,12 +860,6 @@ function DCViewport({ children, minScale = 0.1, maxScale = 8, style = {} }) {
864
860
  // effect's restore-path apply() may already have posted the restored
865
861
  // scale (before miaoda:canvas:present), so clear the guard to re-post it in order.
866
862
  window.parent.postMessage({ type: 'miaoda:canvas:present' }, '*');
867
- // Generic "upgrade" capability signal — announce that this artifact offers
868
- // an upgrade entry so the host can show/hide its upgrade button. Decoupled
869
- // from the canvas zoom-proxy protocol above; any product may post the same
870
- // message. Listener (onHostMsg) is already registered above → announce-after-
871
- // register order holds. No client-side behavior here.
872
- window.parent.postMessage({ type: 'miaoda:upgrade:available', kind: 'design-canvas' }, '*');
873
863
  lastPostedScale.current = undefined;
874
864
  apply();
875
865
 
@@ -141,12 +141,8 @@ function IOSDevice({
141
141
  <div style={{ position: 'absolute', top: 0, left: 0, right: 0, zIndex: 10 }}>
142
142
  <IOSStatusBar dark={dark} />
143
143
  </div>
144
- {/* content — isolate:isolate keeps the screen in its own stacking
145
- context so any z-index authored inside it stays *below* the frame
146
- chrome (status bar z:10, island z:50, home indicator z:60); a
147
- sticky/opaque header with a high z-index can no longer paint over
148
- the status bar. */}
149
- <div style={{ height: '100%', display: 'flex', flexDirection: 'column', isolation: 'isolate' }}>
144
+ {/* content */}
145
+ <div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
150
146
  <div style={{ flex: 1, overflow: 'auto' }}>{children}</div>
151
147
  {keyboard && <IOSKeyboard dark={dark} />}
152
148
  </div>
@@ -62,113 +62,107 @@
62
62
  /* END USAGE */
63
63
  // ─────────────────────────────────────────────────────────────────────────────
64
64
 
65
- // Palette follows the Arco/Semi neutral token system the 创意设计模式 "设计风格"
66
- // panel is designed in (Figma node 2008-22724): a solid white card on neutral
67
- // grays instead of the previous warm-glass look.
68
- // text-1 #1F2329 text-2 #646A73 text-3 #86909C
69
- // border #DEE0E3 fill #EBECEE / #F2F3F5 accent(selected/on) #1F2329
70
65
  const __TWEAKS_STYLE = `
71
- .twk-panel{position:fixed;right:16px;top:16px;z-index:2147483646;width:280px;
66
+ .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;
72
67
  max-height:calc(100vh - 32px);display:flex;flex-direction:column;
73
- transform:scale(var(--dc-inv-zoom,1));transform-origin:top right;
74
- background:#fff;color:#1F2329;
75
- border:.5px solid #DEE0E3;border-radius:12px;
76
- box-shadow:0 8px 24px 8px rgba(0,0,0,.04),0 6px 12px rgba(0,0,0,.04),0 2px 6px rgba(0,0,0,.04);
77
- font:12px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}
68
+ transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right;
69
+ background:rgba(250,249,247,.78);color:#29261b;
70
+ -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);
71
+ border:.5px solid rgba(255,255,255,.6);border-radius:14px;
72
+ box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);
73
+ font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}
78
74
  .twk-hd{display:flex;align-items:center;justify-content:space-between;
79
- padding:12px 12px 12px 16px;cursor:move;user-select:none;
80
- border-bottom:.5px solid #EBECEE}
81
- .twk-hd b{font-size:14px;font-weight:600;letter-spacing:.01em;color:#1F2329}
82
- .twk-x{appearance:none;border:0;background:transparent;color:#86909C;
83
- width:24px;height:24px;border-radius:6px;cursor:default;font-size:13px;line-height:1}
84
- .twk-x:hover{background:#F2F3F5;color:#1F2329}
85
- .twk-body{padding:14px 16px 16px;display:flex;flex-direction:column;gap:14px;
75
+ padding:10px 8px 10px 14px;cursor:move;user-select:none}
76
+ .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}
77
+ .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);
78
+ width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}
79
+ .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}
80
+ .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;
86
81
  overflow-y:auto;overflow-x:hidden;min-height:0;
87
- scrollbar-width:thin;scrollbar-color:#DEE0E3 transparent}
82
+ scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}
88
83
  .twk-body::-webkit-scrollbar{width:8px}
89
84
  .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}
90
- .twk-body::-webkit-scrollbar-thumb{background:#DEE0E3;border-radius:4px;
85
+ .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;
91
86
  border:2px solid transparent;background-clip:content-box}
92
- .twk-body::-webkit-scrollbar-thumb:hover{background:#C9CDD4;
87
+ .twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25);
93
88
  border:2px solid transparent;background-clip:content-box}
94
- .twk-row{display:flex;flex-direction:column;gap:8px}
89
+ .twk-row{display:flex;flex-direction:column;gap:5px}
95
90
  .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}
96
91
  .twk-lbl{display:flex;justify-content:space-between;align-items:baseline;
97
- color:#1F2329}
92
+ color:rgba(41,38,27,.72)}
98
93
  .twk-lbl>span:first-child{font-weight:500}
99
- .twk-val{color:#646A73;font-variant-numeric:tabular-nums}
94
+ .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}
100
95
 
101
- .twk-sect{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
102
- color:#86909C;padding:10px 0 0}
96
+ .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
97
+ color:rgba(41,38,27,.45);padding:10px 0 0}
103
98
  .twk-sect:first-child{padding-top:0}
104
99
 
105
- .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:32px;padding:0 10px;
106
- border:.5px solid #DEE0E3;border-radius:8px;
107
- background:#fff;color:inherit;font:inherit;outline:none}
108
- .twk-field:focus{border-color:#1F2329;box-shadow:0 0 0 2px rgba(31,35,41,.1)}
109
- select.twk-field{padding-right:24px;
110
- background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='%234E5969' d='M0 0h10L5 6z'/></svg>");
111
- background-repeat:no-repeat;background-position:right 10px center}
100
+ .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px;
101
+ border:.5px solid rgba(0,0,0,.1);border-radius:7px;
102
+ background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}
103
+ .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}
104
+ select.twk-field{padding-right:22px;
105
+ background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='rgba(0,0,0,.5)' d='M0 0h10L5 6z'/></svg>");
106
+ background-repeat:no-repeat;background-position:right 8px center}
112
107
 
113
108
  .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;
114
- border-radius:999px;background:#EBECEE;outline:none}
109
+ border-radius:999px;background:rgba(0,0,0,.12);outline:none}
115
110
  .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
116
- width:16px;height:16px;border-radius:50%;background:#fff;
117
- border:.5px solid #DEE0E3;box-shadow:0 1px 3px rgba(0,0,0,.16);cursor:default}
118
- .twk-slider::-moz-range-thumb{width:16px;height:16px;border-radius:50%;
119
- background:#fff;border:.5px solid #DEE0E3;box-shadow:0 1px 3px rgba(0,0,0,.16);cursor:default}
111
+ width:14px;height:14px;border-radius:50%;background:#fff;
112
+ border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
113
+ .twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;
114
+ background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
120
115
 
121
116
  .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;
122
- background:#EBECEE;user-select:none}
117
+ background:rgba(0,0,0,.06);user-select:none}
123
118
  .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;
124
- background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.1);
119
+ background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);
125
120
  transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}
126
121
  .twk-seg.dragging .twk-seg-thumb{transition:none}
127
122
  .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;
128
- background:transparent;color:#646A73;font:inherit;font-weight:500;min-height:28px;
123
+ background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;
129
124
  border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2;
130
- overflow-wrap:anywhere;transition:color .15s}
131
- .twk-seg button[aria-checked="true"]{color:#1F2329}
132
-
133
- .twk-toggle{position:relative;width:36px;height:20px;border:0;border-radius:999px;
134
- background:#E4E6E8;transition:background .15s;cursor:default;padding:0}
135
- .twk-toggle[data-on="1"]{background:#1F2329}
136
- .twk-toggle i{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;
137
- background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.2);transition:transform .15s}
138
- .twk-toggle[data-on="1"] i{transform:translateX(16px)}
139
-
140
- .twk-num{display:flex;align-items:center;box-sizing:border-box;min-width:0;height:32px;padding:0 0 0 10px;
141
- border:.5px solid #DEE0E3;border-radius:8px;background:#fff}
142
- .twk-num-lbl{font-weight:500;color:#646A73;cursor:ew-resize;
125
+ overflow-wrap:anywhere}
126
+
127
+ .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;
128
+ background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}
129
+ .twk-toggle[data-on="1"]{background:#34c759}
130
+ .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
131
+ background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
132
+ .twk-toggle[data-on="1"] i{transform:translateX(14px)}
133
+
134
+ .twk-num{display:flex;align-items:center;box-sizing:border-box;min-width:0;height:26px;padding:0 0 0 8px;
135
+ border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)}
136
+ .twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize;
143
137
  user-select:none;padding-right:8px}
144
138
  .twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent;
145
- font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 10px 0 0;
139
+ font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0;
146
140
  outline:none;color:inherit;-moz-appearance:textfield}
147
141
  .twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{
148
142
  -webkit-appearance:none;margin:0}
149
- .twk-num-unit{padding-right:10px;color:#86909C}
143
+ .twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)}
150
144
 
151
- .twk-btn{appearance:none;height:32px;padding:0 14px;border:0;border-radius:8px;
152
- background:#1F2329;color:#fff;font:inherit;font-weight:500;cursor:default}
153
- .twk-btn:hover{background:#333A44}
154
- .twk-btn.secondary{background:#F2F3F5;color:#1F2329}
155
- .twk-btn.secondary:hover{background:#E5E6EB}
145
+ .twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px;
146
+ background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default}
147
+ .twk-btn:hover{background:rgba(0,0,0,.88)}
148
+ .twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit}
149
+ .twk-btn.secondary:hover{background:rgba(0,0,0,.1)}
156
150
 
157
- .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:24px;
158
- border:.5px solid #DEE0E3;border-radius:6px;padding:0;cursor:default;
151
+ .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;
152
+ border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;
159
153
  background:transparent;flex-shrink:0}
160
154
  .twk-swatch::-webkit-color-swatch-wrapper{padding:0}
161
155
  .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}
162
156
  .twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px}
163
157
 
164
- .twk-chips{display:flex;gap:8px}
165
- .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:40px;
158
+ .twk-chips{display:flex;gap:6px}
159
+ .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px;
166
160
  padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default;
167
- box-shadow:0 0 0 .5px rgba(0,0,0,.08),0 1px 2px rgba(0,0,0,.05);
161
+ box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06);
168
162
  transition:transform .12s cubic-bezier(.3,.7,.4,1),box-shadow .12s}
169
163
  .twk-chip:hover{transform:translateY(-1px);
170
- box-shadow:0 0 0 .5px rgba(0,0,0,.14),0 4px 10px rgba(0,0,0,.1)}
171
- .twk-chip[data-on="1"]{box-shadow:0 0 0 2px #fff,0 0 0 3.5px rgba(0,0,0,.85),
164
+ box-shadow:0 0 0 .5px rgba(0,0,0,.18),0 4px 10px rgba(0,0,0,.12)}
165
+ .twk-chip[data-on="1"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85),
172
166
  0 2px 6px rgba(0,0,0,.15)}
173
167
  .twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%;
174
168
  display:flex;flex-direction:column;box-shadow:-1px 0 0 rgba(0,0,0,.1)}
@@ -208,10 +202,6 @@ function useTweaks(defaults) {
208
202
  function TweaksPanel({ title = 'Tweaks', children }) {
209
203
  const [open, setOpen] = React.useState(false);
210
204
  const dragRef = React.useRef(null);
211
- // x = distance from the viewport's right edge, y = distance from its TOP.
212
- // The panel anchors top-right (right/top in CSS) so it clears the host's
213
- // bottom toolbar; both axes still grow inward, so clamp/drag math is
214
- // symmetric with the old bottom-right anchor.
215
205
  const offsetRef = React.useRef({ x: 16, y: 16 });
216
206
  const PAD = 16;
217
207
 
@@ -220,13 +210,13 @@ function TweaksPanel({ title = 'Tweaks', children }) {
220
210
  if (!panel) return;
221
211
  const w = panel.offsetWidth, h = panel.offsetHeight;
222
212
  const maxRight = Math.max(PAD, window.innerWidth - w - PAD);
223
- const maxTop = Math.max(PAD, window.innerHeight - h - PAD);
213
+ const maxBottom = Math.max(PAD, window.innerHeight - h - PAD);
224
214
  offsetRef.current = {
225
215
  x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)),
226
- y: Math.min(maxTop, Math.max(PAD, offsetRef.current.y)),
216
+ y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)),
227
217
  };
228
218
  panel.style.right = offsetRef.current.x + 'px';
229
- panel.style.top = offsetRef.current.y + 'px';
219
+ panel.style.bottom = offsetRef.current.y + 'px';
230
220
  }, []);
231
221
 
232
222
  React.useEffect(() => {
@@ -263,11 +253,11 @@ function TweaksPanel({ title = 'Tweaks', children }) {
263
253
  const r = panel.getBoundingClientRect();
264
254
  const sx = e.clientX, sy = e.clientY;
265
255
  const startRight = window.innerWidth - r.right;
266
- const startTop = r.top;
256
+ const startBottom = window.innerHeight - r.bottom;
267
257
  const move = (ev) => {
268
258
  offsetRef.current = {
269
259
  x: startRight - (ev.clientX - sx),
270
- y: startTop + (ev.clientY - sy),
260
+ y: startBottom - (ev.clientY - sy),
271
261
  };
272
262
  clampToViewport();
273
263
  };
@@ -284,7 +274,7 @@ function TweaksPanel({ title = 'Tweaks', children }) {
284
274
  <>
285
275
  <style>{__TWEAKS_STYLE}</style>
286
276
  <div ref={dragRef} className="twk-panel" data-miaoda-chrome=""
287
- style={{ right: offsetRef.current.x, top: offsetRef.current.y }}>
277
+ style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>
288
278
  <div className="twk-hd" onMouseDown={onDragStart}>
289
279
  <b>{title}</b>
290
280
  <button className="twk-x" aria-label="Close tweaks"
@@ -353,8 +343,8 @@ function TweakRadio({ label, value, options, onChange }) {
353
343
  valueRef.current = value;
354
344
 
355
345
  // Segments wrap mid-word once per-segment width runs out. The track is
356
- // ~244px (280 panel − 32 body pad − 4 seg pad), each button loses 12px
357
- // to its own padding, and 12px system-ui averages ~6.6px/char — so 2
346
+ // ~248px (280 panel − 28 body pad − 4 seg pad), each button loses 12px
347
+ // to its own padding, and 11.5px system-ui averages ~6.3px/char — so 2
358
348
  // options fit ~16 chars each, 3 fit ~10. Past that (or >3 options), fall
359
349
  // back to a dropdown rather than wrap.
360
350
  const labelLen = (o) => String(typeof o === 'object' ? o.label : o).length;
@@ -565,23 +555,23 @@ Object.assign(window, {
565
555
  @keyframes twk-blink{50%{opacity:0}}
566
556
  @keyframes twk-fadein{from{opacity:0;transform:translateX(4px)}to{opacity:1;transform:none}}
567
557
  .twk-sugg{display:flex;align-items:center;gap:6px;padding:5px 8px;border-radius:8px;
568
- background:#F7F8FA;border:.5px solid #EBECEE;transition:all .15s}
569
- .twk-sugg:focus-within{background:#F2F3F5;border-color:#DEE0E3}
558
+ background:rgba(0,0,0,.04);border:.5px solid rgba(0,0,0,.06);transition:all .15s}
559
+ .twk-sugg:focus-within{background:rgba(0,0,0,.06);border-color:rgba(0,0,0,.12)}
570
560
  .twk-sugg-field{position:relative;flex:1;min-width:0}
571
561
  .twk-sugg-field input{width:100%;height:20px;border:0;background:transparent;
572
562
  font:inherit;outline:none;color:inherit}
573
563
  .twk-sugg-ghost{position:absolute;inset:0;display:flex;align-items:center;
574
- color:#86909C;pointer-events:none;white-space:nowrap;overflow:hidden}
575
- .twk-sugg-ghost.hint{color:#A9AEB8}
564
+ color:rgba(41,38,27,.42);pointer-events:none;white-space:nowrap;overflow:hidden}
565
+ .twk-sugg-ghost.hint{color:rgba(41,38,27,.28)}
576
566
  .twk-sugg-caret{display:inline-block;width:1px;height:13px;margin-left:1px;
577
567
  border-right:1.5px solid currentColor;opacity:.5;animation:twk-blink 1s step-end infinite}
578
568
  .twk-sugg-ideas{appearance:none;border:0;background:transparent;font:inherit;
579
- font-size:10.5px;font-weight:600;color:#646A73;cursor:default;padding:0 2px;
569
+ font-size:10.5px;font-weight:600;color:rgba(41,38,27,.6);cursor:default;padding:0 2px;
580
570
  white-space:nowrap;animation:twk-fadein .25s ease}
581
- .twk-sugg-ideas:hover{color:#1F2329}
571
+ .twk-sugg-ideas:hover{color:rgba(41,38,27,.85)}
582
572
  .twk-sugg-ideas svg{color:#D97757}
583
573
  .twk-sugg-send{appearance:none;border:0;height:20px;padding:0 8px;border-radius:5px;
584
- background:#1F2329;color:#fff;font:inherit;font-size:10px;font-weight:600;cursor:default}
574
+ background:#29261b;color:#fff;font:inherit;font-size:10px;font-weight:600;cursor:default}
585
575
  `;
586
576
  document.head.appendChild(s);
587
577
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/coding-registry-buildless",
3
- "version": "0.1.10",
3
+ "version": "0.1.11-alpha.20260729134521",
4
4
  "description": "Miaoda registry (buildless runtime) — shadcn-style copy-in SDKs (globals + CDN-pinned, browser Babel) for buildless static HTML stacks",
5
5
  "type": "module",
6
6
  "files": [
@@ -38,11 +38,15 @@
38
38
  * a scrollable vertical card list (browse) and a rotated fullscreen
39
39
  * presenting mode. This needs NO authoring changes: never hand-roll
40
40
  * mobile/responsive slide layouts. Desktop behaviour is unchanged.
41
- * Browse mode renders each slide's static base state and never fires
42
- * entrance animations — exactly like the thumbnail rail and print
43
- * (presenting keeps animations). One more surface that requires the
44
- * visible end-state to be the base style (see Entrance animations
45
- * below).
41
+ * Browse mode renders authored CSS animations/transitions at their
42
+ * end state (presenting keeps them) — design animations so the end
43
+ * state is the complete slide; never gate content visibility on an
44
+ * animation having played.
45
+ * (h) preflight mode — add `?preflight=1` to the URL to enter
46
+ * mobile-list layout on any device (including desktop). Identical to
47
+ * mobile browse except the outer frame background is transparent,
48
+ * preventing the model from confusing the frame background with slide
49
+ * content during automated visual inspection.
46
50
  *
47
51
  * Slides are HIDDEN, not unmounted. Non-active slides stay in the DOM with
48
52
  * `visibility: hidden` + `opacity: 0`, so their state (videos, iframes,
@@ -140,7 +144,6 @@
140
144
  const MOB_RADIUS = 8; // card corner radius
141
145
  const MOB_BADGE_IDLE_MS = 2000; // scroll idle before the page badge fades
142
146
  const MOB_UI_HIDE_MS = 3000; // presenting controls auto-hide
143
- const CANVAS_RECAP_MS = 1200; // re-snapshot canvas thumbs once chart entrance animations settle
144
147
 
145
148
  const pad2 = (n) => String(n).padStart(2, '0');
146
149
 
@@ -433,6 +436,12 @@
433
436
  (The equivalent rule further down lives inside @media print only.) */
434
437
  :host([data-mobile-list]) ::slotted([data-deck-skip]) { display: none !important; }
435
438
 
439
+ /* ── Preflight (质检) mode: mobile-list layout with transparent outer
440
+ background so the model doesn't confuse the frame with slide content.
441
+ Activated by ?preflight=1 in the URL. ── */
442
+ :host([data-preflight]) { background: transparent; position: static; overflow: visible; }
443
+ :host([data-preflight]) .stage { background: transparent; position: static; overflow: visible; }
444
+
436
445
  /* ── Mobile presenting: CSS pseudo-landscape (no orientation.lock) ──
437
446
  Rotate only while the device is physically portrait. Once the user
438
447
  turns the phone (system auto-rotate) the viewport itself is landscape,
@@ -703,6 +712,7 @@
703
712
  // windowed margin/shadow, see _fit. A bare load with no #N hash starts at
704
713
  // slide 1, so ?thumbnail=1 alone yields a clean first-slide frame.
705
714
  this._snthumb = /[?&](_snthumb|thumbnail)=/.test(location.search);
715
+ this._preflight = /[?&]preflight=/.test(location.search);
706
716
  this._mobile = isMobileUA();
707
717
  // Layout-driven size signal. window.resize is unreliable inside
708
718
  // prerendered/reframed iOS WebViews and host-managed iframes — the
@@ -715,7 +725,14 @@
715
725
  this._mobSizeRo.observe(document.documentElement);
716
726
  }
717
727
  this._syncMobileMode();
718
- if (this._mobile) this._syncMobFreezeRule();
728
+ if (this._mobile || this._preflight) this._syncMobFreezeRule();
729
+ if (this._preflight) {
730
+ this.setAttribute('data-preflight', '');
731
+ // Override page-level background set by LLM-authored CSS so the
732
+ // preflight screenshot doesn't pick up the theme's body color.
733
+ document.documentElement.style.background = '#fff';
734
+ document.body.style.background = '#fff';
735
+ }
719
736
  if (this._snthumb) this.setAttribute('no-rail', '');
720
737
  this._render();
721
738
  this._loadNotes();
@@ -1039,7 +1056,6 @@
1039
1056
  if (this._scaleRaf) cancelAnimationFrame(this._scaleRaf);
1040
1057
  if (this._liveObserver) this._liveObserver.disconnect();
1041
1058
  if (this._railObserver) this._railObserver.disconnect();
1042
- (this._thumbs || []).forEach((t) => clearTimeout(t._canvasRecapTimer));
1043
1059
  if (this._onTweakChange) window.removeEventListener('tweakchange', this._onTweakChange);
1044
1060
  }
1045
1061
 
@@ -1367,38 +1383,26 @@
1367
1383
  'animation-play-state: running !important; transition-duration: 0s !important; } }';
1368
1384
  }
1369
1385
 
1370
- /** Mobile browse (data-mobile-list) renders each card's static BASE
1371
- * state — the same semantic as the thumbnail rail, print and
1372
- * reduced-motion. The USAGE authoring contract makes the base style the
1373
- * visible end-state (hidden lives only inside keyframes' `from`, gated
1374
- * on [data-deck-active]), so animation-name:none shows exactly the
1375
- * intended slide. Freezing at the END state instead would key visible
1376
- * styling to a flag that MOVES during scroll: keyframe properties
1377
- * (transform especially — it replaces the base value outright) snap on
1378
- * and off as [data-deck-active] arrives at and leaves each card,
1379
- * jolting layout. The timing longhands stay as a backstop: an authored
1380
- * `animation … !important` that out-cascades our name:none still
1381
- * collapses to an instant run. The attribute selector is repeated to
1382
- * out-rank authored !important rules — important-vs-important resolves
1383
- * by specificity, and the common `[data-deck-active] .x` gate is only
1384
- * (0,2,0). Document-level like the print rule: author animations style
1385
- * light-DOM slide content that shadow CSS can't reach, and <deck-stage>
1386
- * is a light-DOM element document CSS can key on. Presenting/desktop
1387
- * keep their animations — the rule is keyed on the host attribute, no
1388
- * JS toggling needed. */
1386
+ /** Mobile browse (data-mobile-list) force-shows every card at once, so
1387
+ * authored CSS animations all fire together on load — and the ones gated
1388
+ * on [data-deck-active] re-fire as scrolling moves that flag between
1389
+ * cards. Freeze them at their end state while browsing. Same jump-to-end
1390
+ * recipe as the print rule above (NOT animation:none, so a deck that
1391
+ * keeps a hidden base state still renders its content), keyed on the
1392
+ * host attribute so presenting/desktop keep their animations and no JS
1393
+ * toggling is needed. Document-level like the print rule: author
1394
+ * animations style light-DOM slide content that shadow CSS can't reach,
1395
+ * and <deck-stage> itself is a light-DOM element document CSS can key on. */
1389
1396
  _syncMobFreezeRule() {
1390
1397
  const id = 'deck-stage-mob-freeze';
1391
1398
  if (document.getElementById(id)) return;
1392
- const sel = 'deck-stage[data-mobile-list][data-mobile-list][data-mobile-list]';
1393
1399
  const tag = document.createElement('style');
1394
1400
  tag.id = id;
1395
1401
  tag.textContent =
1396
- sel + ' *, ' + sel + ' *::before, ' + sel + ' *::after {' +
1397
- ' animation-name: none !important;' +
1402
+ 'deck-stage[data-mobile-list] *, deck-stage[data-mobile-list] *::before, deck-stage[data-mobile-list] *::after {' +
1398
1403
  ' animation-delay: -99s !important; animation-duration: .001s !important;' +
1399
1404
  ' animation-iteration-count: 1 !important; animation-fill-mode: both !important;' +
1400
- ' animation-play-state: running !important;' +
1401
- ' transition-duration: 0s !important; transition-delay: 0s !important; }';
1405
+ ' animation-play-state: running !important; transition-duration: 0s !important; }';
1402
1406
  document.head.appendChild(tag);
1403
1407
  }
1404
1408
 
@@ -1546,11 +1550,11 @@
1546
1550
 
1547
1551
  // The two mobile presentations are mutually exclusive. noscale and
1548
1552
  // _snthumb are capture modes whose output must keep desktop geometry,
1549
- // so they opt out of both.
1553
+ // so they opt out of both. ?preflight=1 forces mobile-list on desktop.
1550
1554
  _syncMobileMode() {
1551
- const eligible = this._mobile && !this.hasAttribute('noscale') && !this._snthumb;
1555
+ const eligible = (this._mobile || this._preflight) && !this.hasAttribute('noscale') && !this._snthumb;
1552
1556
  this.toggleAttribute('data-mobile-list', eligible && !this._presenting);
1553
- this.toggleAttribute('data-mobile-present', eligible && !!this._presenting);
1557
+ this.toggleAttribute('data-mobile-present', eligible && !!this._presenting && !this._preflight);
1554
1558
  }
1555
1559
 
1556
1560
  _fitMobileList() {
@@ -1956,7 +1960,7 @@
1956
1960
  // it scales clones by the rail's _thumbScale, so rewrite the
1957
1961
  // transform for this strip's fixed 132px frames.
1958
1962
  const entry = { slide: e2.slide, frame, host: null, clone: null };
1959
- this._materialize(entry, false); // throwaway entry (not in _thumbs) — skip the recap timer
1963
+ this._materialize(entry);
1960
1964
  if (entry.clone) entry.clone.style.transform = 'scale(' + (132 / this.designWidth) + ')';
1961
1965
  return { el: t, i: e2.i };
1962
1966
  });
@@ -2078,7 +2082,6 @@
2078
2082
  // Orphans — slides removed since last render.
2079
2083
  bySlide.forEach((t) => {
2080
2084
  if (this._railObserver) this._railObserver.unobserve(t.frame);
2081
- clearTimeout(t._canvasRecapTimer);
2082
2085
  t.thumb.remove();
2083
2086
  });
2084
2087
  // Put thumbs into document order to match _slides. insertBefore on
@@ -2260,7 +2263,7 @@
2260
2263
  }
2261
2264
 
2262
2265
  /** Lazily build the clone for a thumb that has scrolled into view. */
2263
- _materialize(entry, scheduleRecap = true) {
2266
+ _materialize(entry) {
2264
2267
  if (entry.host) return;
2265
2268
  const dw = this.designWidth, dh = this.designHeight;
2266
2269
  let clone = entry.slide.cloneNode(true);
@@ -2292,7 +2295,6 @@
2292
2295
  // drawings appear in the thumbnail instead of a blank rectangle.
2293
2296
  const origCanvases = entry.slide.querySelectorAll('canvas');
2294
2297
  const cloneCanvases = clone.querySelectorAll('canvas');
2295
- const canvasImgs = []; // {orig, img} pairs kept for the delayed re-snapshot below
2296
2298
  cloneCanvases.forEach((el, ci) => {
2297
2299
  const orig = origCanvases[ci];
2298
2300
  if (!orig || !orig.width || !orig.height) return;
@@ -2304,7 +2306,6 @@
2304
2306
  ';width:' + orig.offsetWidth + 'px;height:' + orig.offsetHeight + 'px;';
2305
2307
  img.className = el.className;
2306
2308
  el.replaceWith(img);
2307
- canvasImgs.push({ orig, img });
2308
2309
  } catch (e) {}
2309
2310
  });
2310
2311
  // Images: defer decode and let the browser pick the smallest
@@ -2369,30 +2370,6 @@
2369
2370
  // Once materialized the IO callback is a no-op early-return —
2370
2371
  // unobserve so scroll doesn't keep firing it.
2371
2372
  if (this._railObserver) this._railObserver.unobserve(entry.frame);
2372
- // Chart libraries (ECharts/Chart.js) animate their entrance over ~1s and
2373
- // draw straight to the canvas bitmap — no DOM mutation, so _liveObserver
2374
- // never sees the finished frame and the toDataURL snapshot above may have
2375
- // caught a blank or mid-animation canvas. Re-snapshot once after the
2376
- // animation settles and overwrite each <img>. Guarded so a thumb that was
2377
- // refreshed (host swapped) or removed (img detached) since is left alone.
2378
- // scheduleRecap=false for callers whose entry isn't tracked in _thumbs
2379
- // (the mobile filmstrip builds throwaway entries the cleanup paths can't
2380
- // reach) — otherwise the timer leaks and, since that path clones the
2381
- // whole deck eagerly, N recaps would fire together as a toDataURL storm.
2382
- if (scheduleRecap && canvasImgs.length) {
2383
- clearTimeout(entry._canvasRecapTimer);
2384
- entry._canvasRecapTimer = setTimeout(() => {
2385
- entry._canvasRecapTimer = null;
2386
- if (entry.host !== host) return;
2387
- canvasImgs.forEach(({ orig, img }) => {
2388
- // orig.isConnected: if the app swapped the chart's <canvas> since,
2389
- // the detached node still reports nonzero width/height — skip it so
2390
- // we don't paint stale pixels (the swap already re-cloned the thumb).
2391
- if (!img.isConnected || !orig.isConnected || !orig.width || !orig.height) return;
2392
- try { img.src = orig.toDataURL(); } catch (e) {}
2393
- });
2394
- }, CANVAS_RECAP_MS);
2395
- }
2396
2373
  }
2397
2374
 
2398
2375
  /** Re-clone a single thumb (live-update path). No-op if the thumb
@@ -2401,7 +2378,6 @@
2401
2378
  _refreshThumb(slide) {
2402
2379
  const entry = (this._thumbs || []).find((t) => t.slide === slide);
2403
2380
  if (!entry || !entry.host) return;
2404
- clearTimeout(entry._canvasRecapTimer);
2405
2381
  entry.host.remove();
2406
2382
  entry.host = entry.clone = null;
2407
2383
  this._materialize(entry);