[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-qwen-qwen-mm-plugins-edu-agent":3,"mdc--pnw5iu-key":37,"related-org-qwen-qwen-mm-plugins-edu-agent":8737,"related-repo-qwen-qwen-mm-plugins-edu-agent":8921},{"slug":4,"name":4,"fn":5,"description":6,"org":7,"tags":12,"stars":26,"repoUrl":27,"updatedAt":28,"license":29,"forks":30,"topics":31,"repo":32,"sourceUrl":35,"mdContent":36},"qwen-mm-plugins-edu-agent","generate math problem solving tutorial videos","Generate step-by-step math problem-solving tutorial videos in Chinese (Mandarin).\nUse when: (1) a user provides a math problem and wants an explanation video,\n(2) someone says \"make a math tutorial\", \"explain this equation\", \"create a\nteaching video for this problem\", \"讲解这道题\", \"生成解题视频\",\n(3) the user wants a Chinese-language math lesson covering formulas, equations,\nor geometric figures, (4) the user shares a math problem in text or LaTeX and\nasks for a video walkthrough, (5) the input is an image_assets\u002F folder\ncontaining problem images — the skill will extract the problem via visual\nrecognition, solve it, and generate a tutorial video. Teaching components are\nrendered as realistic objects (solid opaque panels, 3D cards, SVG figures) with a\nmodern aurora mesh aesthetic.\n",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},"qwen","Qwen","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Fqwen.png","QwenLM",[13,17,20,23],{"name":14,"slug":15,"type":16},"Content Creation","content-creation","tag",{"name":18,"slug":19,"type":16},"Mathematics","mathematics",{"name":21,"slug":22,"type":16},"Education","education",{"name":24,"slug":25,"type":16},"Video","video",54,"https:\u002F\u002Fgithub.com\u002FQwenLM\u002FQwen-MM-Plugins","2026-08-05T05:58:40.128581",null,0,[],{"repoUrl":27,"stars":26,"forks":30,"topics":33,"description":34},[],"Make any agent harness multimodal-native.","https:\u002F\u002Fgithub.com\u002FQwenLM\u002FQwen-MM-Plugins\u002Ftree\u002FHEAD\u002Fsrc\u002Fcapabilities\u002Fedu-agent\u002Fskill","---\nname: qwen-mm-plugins-edu-agent\ndescription: |\n  Generate step-by-step math problem-solving tutorial videos in Chinese (Mandarin).\n  Use when: (1) a user provides a math problem and wants an explanation video,\n  (2) someone says \"make a math tutorial\", \"explain this equation\", \"create a\n  teaching video for this problem\", \"讲解这道题\", \"生成解题视频\",\n  (3) the user wants a Chinese-language math lesson covering formulas, equations,\n  or geometric figures, (4) the user shares a math problem in text or LaTeX and\n  asks for a video walkthrough, (5) the input is an image_assets\u002F folder\n  containing problem images — the skill will extract the problem via visual\n  recognition, solve it, and generate a tutorial video. Teaching components are\n  rendered as realistic objects (solid opaque panels, 3D cards, SVG figures) with a\n  modern aurora mesh aesthetic.\n---\n\n# Math Tutorial Video Generator\n\nTransforms a math problem into a step-by-step Chinese-language video tutorial. Teaching components — equations, geometric figures, solution steps — are rendered as realistic objects: solid opaque panels (glass-look via borders + layered shadow, NO `backdrop-filter`, NO translucency), 3D metallic cards, animated SVG constructions, on themed backgrounds with animated gradient orbs and smooth ambient effects. The default theme is \"Aurora Scholar\" (blue wave texture + indigo\u002Fviolet\u002Fcyan orbs); 4 alternative light themes are available (清雅湖蓝, 柔紫轻盈, 薄荷清新, 暖黄纸感) for visual variety across different problem types — see the Background Theme Catalog in design-system.md.\n\n## Prerequisites (环境准备 — 开工前必查)\n\nThis is a **skill-only** capability (no MCP server), so its runtime dependencies are **NOT** auto-installed by `uvx`. Verify ALL of the following before Step 0 — a missing one silently breaks a later step:\n\n| Dependency | Needed for | Install \u002F check |\n|------------|-----------|-----------------|\n| **Node.js + npm\u002Fnpx** | scaffold + render (`npx hyperframes`) | `node -v` (≥18) |\n| **hyperframes CLI** | `init` \u002F `lint` \u002F `validate` \u002F `render` | pulled on demand via `npx hyperframes` (needs npm-registry access at scaffold time; the project then pins a version in `dist\u002Fpackage.json`) |\n| **Headless Chromium + OS libs** | `npx hyperframes render` (puppeteer) + post-render QA gates (`postcheck.py` \u002F `precheck.py` drive headless Chrome) | the browser itself is auto-downloaded by puppeteer on first `npx hyperframes`; on **minimal Linux** you must also `apt install libnss3 libatk-bridge2.0-0 libgbm1 libasound2 libxkbcommon0 libgtk-3-0 fonts-noto-cjk` (else Chrome fails to launch, or CJK\u002Fformulas render as tofu boxes). Reuse a system Chrome via `export PUPPETEER_EXECUTABLE_PATH=\u002Fusr\u002Fbin\u002Fchromium`. |\n| **Python 3 + pip** | TTS script | `python3 -m pip --version` |\n| **`dashscope` `soundfile` `numpy` `requests`** | Step 3 TTS synthesis + assembly | `python3 -m pip install dashscope soundfile numpy requests` |\n| **ffmpeg** | loudness normalization (`loudnorm`) + frame extraction for self-check | `brew install ffmpeg` \u002F `apt install ffmpeg` |\n| **`DASHSCOPE_API_KEY`** | Qwen-TTS (`qwen3-tts-flash`) | `export DASHSCOPE_API_KEY=\"sk-xxx\"`, **or** put `DASHSCOPE_API_KEY=sk-xxx` in `~\u002F.qwen-mm-plugins\u002Fconfig` for GUI-launched setups that don't inherit shell exports. `$EDU_SKILL_ROOT\u002Fscripts\u002Fgenerate_voice.py` reads it at runtime — **never `cat`\u002Fpaste the key into the conversation.** |\n\n> **Network boundary:** `npx hyperframes init` and the TTS calls need internet. The *render* itself is air-gapped — that is why fonts \u002F KaTeX \u002F GSAP must be self-hosted into `dist\u002F` (see Step 5 Prerequisites). DashScope TTS may rate-limit under high concurrency; if you hit `Throttling.RateQuota`, lower the thread-pool worker count and add backoff (see step-3).\n\nBefore Step 0, resolve `EDU_SKILL_ROOT` to the **absolute directory containing this `SKILL.md`**. All\nshipped scripts and assets must be addressed through that root; they are not in the user's project.\nShell tool calls do not necessarily share state, so set it in every command block that uses it (or\nsubstitute the resolved absolute path directly); never rely on an earlier shell invocation:\n\n```bash\nEDU_SKILL_ROOT=\"\u003Cabsolute directory containing qwen-mm-plugins-edu-agent\u002FSKILL.md>\"\ntest -f \"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py\" || { echo \"invalid EDU_SKILL_ROOT\"; exit 1; }\n```\n\n## Pipeline Overview\n\n| Step | Name | Artifact | Reference |\n|------|------|----------|-----------|\n| 0 | Image Input & Problem Extraction | `PROBLEM.md` | [step-0-image-input.md](references\u002Fstep-0-image-input.md) |\n| 1 | Problem Analysis | `ANALYSIS.md` | [step-1-problem-analysis.md](references\u002Fstep-1-problem-analysis.md) |\n| 2 | Teaching Script | `SCRIPT.md` | [step-2-teaching-script.md](references\u002Fstep-2-teaching-script.md) |\n| 3 | Voice Generation | `narration.wav` + `transcript.json` + `captions.json` (sentence-by-sentence TTS, no Whisper) | [step-3-voice-generation.md](references\u002Fstep-3-voice-generation.md) |\n| 4 | Storyboard | `STORYBOARD.md` | [step-4-storyboard.md](references\u002Fstep-4-storyboard.md) |\n| 5 | Build Components | `compositions\u002F*.html` | [step-5-build-components.md](references\u002Fstep-5-build-components.md) |\n| 6 | Compose & Render | `index.html` + MP4 | [step-6-compose-render.md](references\u002Fstep-6-compose-render.md) |\n\n## Step 0: Image Input & Problem Extraction\n\nRead [references\u002Fstep-0-image-input.md](references\u002Fstep-0-image-input.md).\n\nRead all images from the `image_assets\u002F` folder using the `Read` tool (Claude's multimodal vision directly interprets image content). Extract the complete problem text, convert all math expressions to LaTeX, and describe any figures or diagrams. If JSONL metadata is available (subject, sub_subject, question_type, stepwise_explanation), use it as context hints but treat the image as ground truth.\n\n\u003CHARD-GATE>\n`PROBLEM.md` must exist with: complete problem text in Chinese, all math expressions in LaTeX, and figure descriptions (if applicable). All images in `image_assets\u002F` must have been read.\n\u003C\u002FHARD-GATE>\n\n## Step 1: Problem Analysis\n\nRead [references\u002Fstep-1-problem-analysis.md](references\u002Fstep-1-problem-analysis.md).\n\nParse the input math problem, classify its type, extract knowledge points, and produce a complete solution outline with numbered steps. When `PROBLEM.md` exists from Step 0, use it as the primary input source.\n\n\u003CHARD-GATE>\n`ANALYSIS.md` must exist with: problem statement, type classification, solution steps, and final answer — all verified for correctness.\n\u003C\u002FHARD-GATE>\n\n## Step 2: Teaching Script\n\nRead [references\u002Fstep-2-teaching-script.md](references\u002Fstep-2-teaching-script.md).\n\nWrite the Chinese narration script with scene markers. Apply math symbol pronunciation rules. Target pacing: 3.5-4.0 Chinese characters per second.\n\n\u003CHARD-GATE>\n`SCRIPT.md` must exist with scene-separated narration text in Chinese. All math symbols converted to spoken Chinese.\n\u003C\u002FHARD-GATE>\n\n## Step 3: Voice Generation\n\nRead [references\u002Fstep-3-voice-generation.md](references\u002Fstep-3-voice-generation.md). TTS strategy: **DashScope Qwen-TTS via the official SDK** (`dashscope.MultiModalConversation`, model `qwen3-tts-flash`, HTTP — returns a WAV URL) — no self-hosted TTS node, no URL wiring. **Speed + accuracy:** synthesize every sentence **concurrently** through a thread pool (wall-clock ≈ the slowest single sentence, not the sum), then measure each returned clip's real duration for exact per-sentence timestamps. No Whisper dependency.\n\nGenerate standard Mandarin TTS audio. Each sentence's exact duration is measured from its returned audio clip, producing both `narration.wav` and `transcript.json` in one pass with 100% accurate timestamps. No Whisper transcription needed.\n\n\u003CHARD-GATE>\n`narration.wav`, `transcript.json`, and `captions.json` must exist. Audio must be loudness-normalized with `ffmpeg loudnorm` (EBU R128, -16 LUFS). Audio is generated with DashScope Qwen-TTS (`dashscope.MultiModalConversation`, model `qwen3-tts-flash`) — `DASHSCOPE_API_KEY` required. Timestamps are measured from TTS output (not estimated). Text in transcript\u002Fcaptions comes from the original script. Timestamps mapped to scene boundaries.\n\u003C\u002FHARD-GATE>\n\n## Step 4: Storyboard\n\nRead [references\u002Fstep-4-storyboard.md](references\u002Fstep-4-storyboard.md). Read [design-system.md](design-system.md) for visual tokens.\n\nDesign per-scene visual layout, assign component templates from [math-components.md](references\u002Fmath-components.md), and plan transitions. Check [assets\u002FASSET_CATALOG.md](assets\u002FASSET_CATALOG.md) to identify which pre-built visual components can be reused in each scene. **Select a background theme** from the Background Theme Catalog in design-system.md — set it in the Global Direction block (default: `aurora-scholar`).\n\n\u003CHARD-GATE>\n`STORYBOARD.md` must exist with per-scene direction: component type, layout, animation choreography, and transition choice.\n\u003C\u002FHARD-GATE>\n\n## Step 5: Build Components\n\nRead the `hyperframes` skill — every composition authoring rule applies. Read the `gsap` skill for animation patterns. Read [references\u002Fstep-5-build-components.md](references\u002Fstep-5-build-components.md). Read [references\u002Fmath-components.md](references\u002Fmath-components.md) for component templates. Read [assets\u002FASSET_CATALOG.md](assets\u002FASSET_CATALOG.md) for pre-built visual components. **For geometry diagrams (几何图形)**, read [references\u002Fgeometry-construction-guide.md](references\u002Fgeometry-construction-guide.md) for coordinate computation patterns, angle arc construction, and complete worked examples (triangles, quadrilaterals, circles, rotations, reflections); also read [references\u002Fgolden-example-geometry.md](references\u002Fgolden-example-geometry.md) for a complete golden pipeline (PROBLEM → HTML compositions) showing the proven split layout, draw-on animation choreography, and narration-synced note blocks from a top-quality geometry proof video. **For circuit schematic diagrams (电路图)**, read [references\u002Fcircuit-schematic-guide.md](references\u002Fcircuit-schematic-guide.md) for physics rules and SVG symbol templates. **For any scene that must ANIMATE A PROCESS with real motion (生物过程\u002F受力矢量\u002F波动\u002F滴定\u002F运动演示 — things that split, move, get pulled, or a quantity that changes across stages), read [references\u002Fexample-process-animation.md](references\u002Fexample-process-animation.md)** — a top-scoring few-shot (洋葱有丝分裂) with two complete reference scenes and copy-me techniques: staged phase reveal, split-and-move, **connectors that track a moving object and shorten (纺锤丝\u002F绳\u002F矢量牵引)**, live count-up, and a stepped quantity-vs-stage chart (plus the no-360°-spin rule). **🧬 MANDATORY for ANY 染色体\u002F细胞分裂题目 (有丝分裂\u002F减数分裂\u002F染色单体\u002F着丝点\u002F纺锤丝\u002F移向两极): you MUST open and copy [references\u002Fexamples\u002Fmitosis-anaphase.scene.html](references\u002Fexamples\u002Fmitosis-anaphase.scene.html)** — the spindle fibers must be animated `\u003Cline>`s that TRACK the moving chromatids and SHORTEN (pull them to the poles), with a live 染色体数 count-up, and NO 360° spin on chromosomes. This is enforced at render time by `scripts\u002Fcheck_chromosome_example.py` (a chromosome cell-division scene without the fiber-shorten tween, or with a 360° spin, FAILS precheck).\n\nScaffold the project with `npx hyperframes init dist --non-interactive --example blank` (the `--example blank` flag is required — hyperframes ≥0.7.77 rejects a bare `--non-interactive`). **The project root is the current workspace and the scaffold dir MUST be exactly `dist\u002F` at that root — never nest it inside another folder (e.g. NOT `math-tutorial-output\u002Fdist`); the renderer and all gates assume `.\u002Fdist`.** **Build ONE composition per scene listed in `STORYBOARD.md` — one special ray \u002F one solution step \u002F one concept per scene; NEVER cram several planned scenes into a single composition (that is the #1 cause of under-rendered, blank, or half-empty scenes and of run-to-run instability).** **Before writing custom HTML for any visual object**, check the [assets\u002F](assets\u002F) directory — it contains 83 pre-built K12 components (motion, optics, circuit, mechanics, fluid, chemistry, wave, indicators, math), including 6 circuit schematic symbols (`sch-battery`, `sch-ammeter`, `sch-voltmeter`, `sch-switch`, `sch-bulb`, `sch-resistor`). If a matching component exists, copy its CSS + HTML + JS hooks verbatim into the composition instead of building from scratch.\n\n\u003CHARD-GATE>\n**>>> MANDATORY: run precheck and loop until it passes \u003C\u003C\u003C**\n\n```bash\nEDU_SKILL_ROOT=\"\u003Cabsolute directory containing qwen-mm-plugins-edu-agent\u002FSKILL.md>\"\npython3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py\" dist\n```\n\nThis script auto-fixes LaTeX escaping, then runs all validation checks. **You MUST see `ALL CHECKS PASSED` in the output before proceeding to Step 6.** If any check shows `FAIL`:\n\n1. Read the FAIL message — it prints the exact file, line, and fix instruction\n2. Apply the fix to the offending file(s)\n3. Re-run `python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py\" dist`\n4. **Repeat steps 1-3 until the output contains `ALL CHECKS PASSED`**\n\n**Do NOT proceed to Step 6 with failing checks. Do NOT skip precheck. Do NOT treat precheck failures as warnings.**\n\n---\n\nThe precheck validates all of the following (each is also a standalone script in `scripts\u002F`):\n\n- **Asset mirror** — GSAP\u002FKaTeX\u002Ffonts mirrored into `compositions\u002F` (sub-compositions resolve `.\u002F` relative to their own directory; without the mirror, GSAP\u002FKaTeX\u002Ffonts silently fail → blank scenes)\n- **Composition format** — sub-compositions are HTML fragments, not full `\u003C!doctype>` documents (full docs → blank panels)\n- **No CDN URLs** — no `cdn.jsdelivr.net` or external `src`\u002F`href` (render sandbox is air-gapped → all external loads fail silently)\n- **KaTeX CJK** — no Chinese inside `katex.render()` or `data-tex` (KaTeX fonts lack CJK → tofu)\n- **CJK font on Chinese text (中文必须用中文字体)** — any SVG `\u003Ctext>`\u002F`\u003Ctspan>` or inline-styled element whose text contains Chinese MUST use a stack including `Noto Sans SC` (e.g. `font-family=\"Noto Sans SC, Inter, sans-serif\"`); `Inter`\u002F`sans-serif` alone has no CJK glyphs so Chinese renders as **NO GLYPH \u002F 豆腐块**. Don't mix scripts in one `\u003Ctext>` — split Latin (Inter) and Chinese (Noto Sans SC) into separate `\u003Ctspan>`\u002F`\u003Ctext>`. Gate: `scripts\u002Fcheck_cjk_font.py`\n- **KaTeX escaping** — LaTeX in JS strings double-escaped (`\\\\\\\\dfrac`, not `\\\\dfrac`; JS eats single `\\\\d` → \"dfrac12\" instead of fraction)\n- **Unrendered fractions** — no literal \"dfrac\"\u002F\"frac{\" leaked into visible HTML text\n- **Caption size** — font-size 36–40px (not 64px giant subtitles)\n- **Caption pinned to bottom (字幕固定在视频下方)** — every caption bar lives ONLY in `index.html`'s root track and is styled `position:absolute; bottom:48px; left:50%; transform:translateX(-50%)`; NEVER use `top:`, and NEVER put a caption inside a scene composition (it would drift to the top\u002Fmiddle). Gate: `scripts\u002Fcheck_caption_position.py`\n- **Caption safe zone** — every scene reserves the bottom ~180px (content in the top ~900px) so the subtitle never covers content (字幕遮挡)\n- **Caption overflow** — the caption bar is width-bounded (`max-width:1600px`, never `white-space:nowrap`), uses `width:max-content` (so it uses the full width before wrapping instead of shrinking to the ~960px half-frame), and each cue is short (split long sentences into sequential one-line cues); the subtitle never runs off the frame edge (字幕超出边界). Gate: `scripts\u002Fcheck_caption_overflow.py`\n- **Caption always on top (字幕必须在最顶层)** — the caption bar has an unbeatable `z-index` (`2147483647`) so it is ALWAYS above every scene layer\u002Fpanel and can never be covered (字幕被遮挡); scenes must keep their z-index small (\u003C100). Gate: `scripts\u002Fcheck_caption_overflow.py`\n- **Scene layout** — `.scene-content` is a centering box that fills the frame (`position:absolute;inset:0;display:flex;align-items:center;justify-content:center`); filling alone (no flex-center trio) piles a single panel at the TOP with the bottom half empty (排版问题). Gate: `scripts\u002Fcheck_scene_layout.py`\n- **No oversize\u002Foverflow (排版过大)** — every element fits within 1920×1080 (content within the 1920×900 safe area with edge margins); no CSS `width`>1920px \u002F `height`>1080px, keep `scale()` ≤ ~1.5; size by container (%\u002Fmax-width\u002Fflex\u002Fgrid), scale SVG via viewBox. Hero text ≤ ~96px is guidance (decorative watermarks may be larger). Gate: `scripts\u002Fcheck_scene_overflow.py`\n- **SVG height must be bounded (SVG高度必须有界 — 防止场景突然变大)** — never size a content `\u003Csvg>` as `width:100%; height:auto` with a tall\u002Fnear-square viewBox: `height:auto` ties the rendered height to the viewBox aspect ratio, so at full panel width a `1000×760` viewBox becomes ~1050px tall and overflows the ~860px usable height — the scene appears to \"suddenly get big\" (场景突然变大). Bound the height: give the SVG `max-height:\u003Cusable>px` (e.g. `max-height:760px`), or size it by height inside a bounded flex\u002Fgrid box with `preserveAspectRatio=\"xMidYMid meet\"`, or choose a viewBox whose aspect ratio matches the available box. Deterministic gate: `scripts\u002Fcheck_svg_height_bound.py`; the headless-measured `scripts\u002Fcheck_scene_fit.py` is the runtime backstop.\n- **No overlapping SVG labels (SVG标签禁止重叠)** — inside a single `\u003Csvg>`, no two `\u003Ctext>` labels may collide. Don't stack a fulcrum\u002Fcenter label and its arm\u002Fsegment labels on one shared line; put the center label above and arm labels below (anchored `end`\u002F`start`, pushed outward past the shape edge) with a gap of ≥ ~1 label-height; stagger point labels that share an axis and never let a point label bury an axis tick number (see Rule #28). Gate: `scripts\u002Fcheck_svg_label_overlap.py`\n- **Node-graph diagrams clean (关系图\u002F流程图)** — in a boxes+arrows+in-shape-label diagram: every in-shape label is in the same `\u003Cg>` as its shape (else it slides off → white字消失), white SVG text only ever sits inside a dark shape, arrow endpoints stop before the 方框 (no arrowhead poking into a box), edge labels stay in the gaps (see Rule #31). Gate: `scripts\u002Fcheck_svg_node_graph.py`\n- **No CSS-hidden content** — no `opacity:0` \u002F `visibility:hidden` in CSS; GSAP handles via `autoAlpha:0` in JS\n- **No frosted glass** — no `backdrop-filter`, panels are opaque `#ffffff`\n- **Scene coverage** — one composition per storyboard scene, all wired with `data-composition-src` and `window.__timelines[...]`\n- **Root refs** — `index.html` uses `data-composition-src` (not `data-src` or `src`) for all scenes\n- **Geometry verification** — every geometry scene (SVG with 3+ labeled points) has a `\u003C!-- GEOMETRY VERIFICATION -->` block; all `ASSERT` lines verified mathematically (coordinates satisfy parallel\u002Fperpendicular\u002Fmidpoint\u002Fintersection\u002Fratio constraints)\n\nAdditional requirements NOT checked by precheck (manual verification):\n- Self-hosted fonts + KaTeX + GSAP copied into `dist\u002F` AND mirrored into `dist\u002Fcompositions\u002F`\n- Chinese font NOT subsetted — one full `NotoSansSC-Bold.woff2` per weight\n- KaTeX CSS inlined with local font URLs\n- No gradient text on KaTeX formulas (use solid `color`, not `background-clip:text`)\n- Grid\u002Flattice figures generated programmatically with deduped edges (not hand-typed coordinates)\n\u003C\u002FHARD-GATE>\n\n## Step 6: Compose & Render\n\nRead [references\u002Fstep-6-compose-render.md](references\u002Fstep-6-compose-render.md). Invoke the `hyperframes-cli` skill for CLI commands.\n\nAssemble root `dist\u002Findex.html`, run lint + validate + inspect, preview, and render. **All CLI commands (lint, validate, render) must run from inside `dist\u002F`.**\n\n**🔎 MANDATORY after rendering — visual overlap self-check loop (渲染后抽帧自查，看到重叠必须改坐标重渲，直到不重叠):** `precheck.py` now includes `check_render_overlap.py`, a headless render-truth gate that measures REAL bounding boxes and FAILs when a `\u003Ctext>` is painted under\u002Fover a box it doesn't belong to (文字被方框覆盖\u002F压框 — e.g. 取食→取, 捕食→捕; static coordinate gates miss this because scaling + CJK glyph width + SVG paint order make a \"clear on paper\" label collide in the render). That gate must pass. **In addition, you MUST look at the pixels:** for every scene with boxes\u002Fconnectors\u002Flabels\u002Fcharts, extract a settled frame (`ffmpeg -ss \u003Cscene_midpoint> -i output.mp4 -frames:v 1 \u002Ftmp\u002Fov.png`), open it with the Read tool, and check by eye for **文字被框压\u002F裁切、框和线重叠、框对不齐，以及方向\u002F朝向\u002F语义错误**（figure 画反\u002F朝向错：燃着木条火焰须在瓶内而非瓶口外、斜面朝向、箭头矢量方向、仪器插入端、倾倒口朝向、电池极性\u002F电流方向、图表是否真的体现所述趋势）. If you see ANY overlap\u002Fmisalignment\u002Fwrong-orientation, **edit the offending coordinates (move labels into the gap with clearance \u002F shorten connectors \u002F align boxes \u002F draw highlight rings inside the box \u002F flip the reversed figure), re-render, and look again — repeat until every frame is visually clean and physically correct.** Never claim \"visually verified\" without having actually extracted and read the frames. Full loop in [references\u002Fstep-6-compose-render.md](references\u002Fstep-6-compose-render.md) → \"Post-Render Visual Overlap Self-Check\".\n\n**🚫 MANDATORY after rendering — line\u002Fcurve render-truth gate (线\u002F曲线漏画检测):** run `python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fpostcheck.py\" dist` and loop until it prints `ALL POST-RENDER CHECKS PASSED`. Its `check_curves_rendered.py` is renderer-agnostic: it renders a KNOWN-GOOD reference of each scene (GSAP + fonts injected by absolute path), then checks that every solid line\u002Fcurve that scene should draw is **actually painted in `output.mp4`**. This catches the most deceptive render bug — a graph that shows its dots\u002Faxes\u002Flabels but drops the connecting curves (**\"有点、没线\"**) because JS-generated path `d` never ran (typically a hand-rolled renderer that stripped GSAP without re-injecting it by absolute `file:\u002F\u002F`). Pre-render gates cannot catch this — the composition is correct; only the rendered pixels reveal the loss. **Prevention: render with `npx hyperframes render`; do NOT hand-roll a CDP\u002Fpuppeteer renderer — and if you must, re-inject GSAP\u002FKaTeX\u002Ffonts via absolute `file:\u002F\u002F` paths (as you already do for bg-texture), never strip `.\u002Fgsap\u002Fgsap.min.js` and leave it.** Full section in [references\u002Fstep-6-compose-render.md](references\u002Fstep-6-compose-render.md) → \"Post-Render Line\u002FCurve Render-Truth Gate\".\n\n**🌊 MANDATORY after rendering — smooth-curve render-truth gate (曲线不能用折线逼近，看到折线必须改代码重渲，直到通过):** the same `postcheck.py` now also runs `check_smooth_curve_render.py`. It loads each continuous-curve scene (抛物线\u002F双曲线\u002F正弦\u002F指数\u002F反比例衰减 …), seeks the animation to its settled state, then **walks the ACTUAL rendered curve geometry** (`getPointAtLength`) and FAILs when a curve is drawn as a handful of straight chords — a jagged zig-zag with sharp kinks and long dead-straight runs between them (`\u003Cpath d=\"M488,585 L500,540 L530,480 …\">` 这类 ~7 段直线拼出来的\"曲线\"). This is a **visual\u002Frender-truth** check: it sees the rendered shape, so it also catches curves whose `d` is generated by JS (which the static pre-render `check_smooth_curve.py` cannot see). **When it FAILs, you MUST rewrite that curve's code and re-render, looping until it passes** — do NOT ship a jagged curve. **Fix:** build the path `d` with the Catmull-Rom `smoothPath()` helper (sparse control points → cubic Béziers), OR emit a densely-sampled point list (~1 pt per ≤10 px, ~120+ pts across a wide axis). Genuinely piecewise-linear graphs (`y=|x|`, 分段函数, 折线统计图, 匀速距离-时间) are exempt. See references\u002Fstep-5-build-components.md → \"Smooth curves (顺滑曲线)\".\n\n\u003CHARD-GATE>\n**>>> Pre-render gate: precheck must have passed \u003C\u003C\u003C**\n\nBefore rendering, confirm that `python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py\" dist` was already run in Step 5 and printed `ALL CHECKS PASSED`. If you skipped it or are unsure, run it now — it takes seconds. Do NOT render with failing prechecks.\n\n`npx hyperframes lint` and `npx hyperframes validate` pass with zero errors (run from `dist\u002F`). Video rendered to MP4 via `npx hyperframes render` inside `dist\u002F`. The output `.mp4` file must exist inside `dist\u002F` and be reported to the user. Preview-only is NOT sufficient — rendering is mandatory. **After rendering: `check_render_overlap.py` passes AND you have extracted+viewed a frame from every box\u002Fconnector\u002Flabel\u002Fchart scene and confirmed no clipped\u002Fcovered text, no line-into-box, no misaligned boxes — fixing coordinates and re-rendering until clean. AND `python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fpostcheck.py\" dist` prints `ALL POST-RENDER CHECKS PASSED` (every line\u002Fcurve the scene should draw is actually painted in output.mp4 — 线\u002F曲线没漏画).**\n\u003C\u002FHARD-GATE>\n\n## Design System\n\nRead [design-system.md](design-system.md) before writing ANY HTML. It defines the \"Aurora Scholar\" light-themed visual identity. Use its exact color tokens, font specs, and component styles. Do not invent colors.\n\n## Problem Type Reference\n\n| Problem Type | Typical Duration | Scenes | Key Components |\n|---|---|---|---|\n| Single equation | 30-60s | 4-5 | Problem Card + 2-3 Formula Panels + Conclusion |\n| Multi-step algebra | 60-120s | 6-8 | Problem Card + Analysis + Steps + Summary |\n| Geometry proof | 90-150s | 7-10 | Problem Card + Geometry Canvas + Proof Steps |\n| Word problem | 60-90s | 5-7 | Problem Card + Modeling + Solve + Verify |\n\n## Non-Negotiable Rules\n\n1. **Caption text from original script (字幕必须用原始脚本文本).** Video captions\u002Fsubtitles MUST use text from `captions.json` (which contains the original narration script text with timestamps measured from TTS output). The timestamps are 100% accurate because they are measured from each sentence's actual TTS audio duration during sentence-by-sentence synthesis. The OpenCC `t2s` conversion is no longer needed — transcript text comes directly from the original script.\n2. **KaTeX for layout-dependent math; plain HTML\u002FChinese for simple symbols (复杂公式用KaTeX，简单符号用HTML或中文).** Use KaTeX only when the expression needs math layout features (fractions, roots, summations, matrices). For simple comparisons and standalone symbols (≤, ≥, °, ×), prefer HTML entities or Chinese text directly — e.g., write `\u003Cspan>OP' ≤ 1\u003C\u002Fspan>` not `katex.render(\"OP' \\\\leq 1\", ...)` — this avoids the fragile JS backslash-escaping pipeline entirely (see Rule #25). Never display raw LaTeX source code. **When you DO use KaTeX in JS strings, ALWAYS double-escape LaTeX backslashes** — write `\"\\\\\\\\dfrac{1}{2}\"` not `\"\\\\dfrac{1}{2}\"`. The `\\\\d` escape is silent: JS turns `\\\\d` → `d`, so `\\\\dfrac` becomes `dfrac` and KaTeX renders italic text \"dfrac12\" instead of a fraction. This is the single most common rendering bug. After building all compositions, run `python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fcheck_katex_escaping.py\" dist` and fix every reported line BEFORE proceeding to Step 6.\n3. **Chinese pacing.** Narration at 3.5-4.0 characters\u002Fsecond. Leave 0.5-1.0s pauses between steps.\n4. **Three layers per scene.** Background treatment (wave texture + aurora mesh orbs) + content layer + accent elements. No flat single-layer scenes. See design-system.md \"Background Treatment\" for the exact CSS pattern and aurora palette guide.\n5. **Solid opaque panels — frosted glass is FORBIDDEN (禁止毛玻璃，完全避免遮挡).** All content panels use the solid panel style from design-system.md — OPAQUE `background:#ffffff` (or white alpha ≥ 0.92), depth from borders + layered box-shadow + inset top highlight. **NEVER** use `backdrop-filter` \u002F `-webkit-backdrop-filter`, and **NEVER** a see-through translucent panel background — a blurred\u002Fsee-through panel washes out and OCCLUDES the problem text \u002F diagram \u002F formulas behind it (this is a hard defect). Enforced by `scripts\u002Fcheck_no_glass.py` (in `precheck.py`): render is blocked if any glass\u002Ftranslucent panel is found.\n6. **SVG geometry.** Geometric figures use SVG path drawing animation, never static images.\n7. **Deterministic.** No `Math.random()`, `Date.now()`, or async timeline construction.\n8. **Delegate.** Use the `hyperframes` skill for composition rules, `hyperframes-cli` for CLI commands. TTS uses **DashScope Qwen-TTS via the official SDK** (`dashscope.MultiModalConversation`, model `qwen3-tts-flash`, HTTP) — no self-hosted node, no URL wiring; needs `DASHSCOPE_API_KEY`. Synthesize sentences **concurrently** (thread pool) for speed, and take per-sentence timestamps from each returned clip's measured duration (see step-3-voice-generation.md). This skill defines the math-tutorial domain logic only.\n9. **Dark text on light backgrounds (浅色背景深色文字).** This is a light-theme design system. All text — KaTeX equations, SVG labels, Chinese body text, HTML table cells — MUST use dark colors (`#0f172a` or darker). Every composition MUST include the full \"Mandatory Global Color Reset\" block from design-system.md: root selector with `color: #0f172a`, `.katex, .katex * { color: #0f172a; }`, `.katex-mathml { display: none !important; }`, and `table, th, td { color: #0f172a; }`. Never use `#fff`, `#f8fafc`, `#e8ecf4`, or any light color for text on the light background.\n10. **Pre-built assets first.** Before writing custom HTML\u002FCSS for any visual object (car, train, candle, battery, lens, etc.), check [assets\u002FASSET_CATALOG.md](assets\u002FASSET_CATALOG.md). If a matching component exists, copy its CSS and HTML verbatim. If NO match exists, follow the \"Quality Fallback Template\" in ASSET_CATALOG.md — every custom object must have ≥3 gradient layers, inset shadows, ground shadow, glow halo, and no CSS @keyframes. Compare against the candle component for quality level.\n11. **No emoji.** Never use emoji characters (🔧⚙️🔵🔴🟢⭕📐✅❌⚡💡 etc.) anywhere in visible text — titles, labels, captions, formula notes, phase titles, badge text, or any string rendered in the video. Headless Chromium has no emoji font installed; all emoji render as □ (tofu boxes) in the final video. Use plain Chinese text or SVG\u002FCSS shapes instead.\n12. **Math symbols: simple via HTML entities, complex via KaTeX — NO Chinese inside KaTeX (简单符号用HTML实体，复杂公式用KaTeX，KaTeX禁止包含中文).** Common math comparison and operator symbols are safe as HTML entities in Noto Sans SC \u002F Inter: ≤ (`&le;`), ≥ (`&ge;`), ≠ (`&ne;`), ° (`&deg;`), × (`&times;`), ÷ (`&divide;`), ± (`&plusmn;`), ² (`&sup2;`), ³ (`&sup3;`) — use these directly in HTML text instead of KaTeX when no layout structure is needed (see Rule #25). **Greek letters (α, β, γ, δ, ε, θ, λ, μ, π, φ, ω) are safe as Unicode characters in HTML text** — Inter-Variable.woff2 contains all Greek glyphs (Noto Sans SC does NOT). The font-family stack `\"Noto Sans SC\", Inter, sans-serif` will fall back to Inter for Greek. **NEVER write the English word** (\"alpha\", \"beta\", \"theta\") **— always use the Unicode character** (α, β, θ). Do NOT insert other specialized Unicode math symbols (∠, △, ⊥, ∥, √, ∞, ₁, ₂, etc.) directly into HTML text — use KaTeX for these in formulas, or Chinese equivalents in non-formula context (\"角ABC\" not \"∠ABC\", \"三角形\" not \"△\"). For inline subscripts\u002Fsuperscripts in labels, use HTML `\u003Csub>`\u002F`\u003Csup>` tags (e.g., `F\u003Csub>1\u003C\u002Fsub>` not `F₁`). The only safe non-ASCII characters in plain text are standard CJK Unified Ideographs (U+4E00–U+9FFF), common CJK punctuation, the HTML-entity math symbols listed above, and Greek letters (U+0370–U+03FF). **CRITICAL: Never put Chinese text inside KaTeX `\\text{...}` or any KaTeX command.** KaTeX's math fonts (KaTeX_Main, KaTeX_Math, etc.) do NOT contain CJK glyphs — any Chinese character inside `\\text{}` renders as □ tofu boxes. When a formula needs to be mixed with Chinese text, break it into separate HTML elements:\n    ```html\n    \u003C!-- WRONG — Chinese in \\text{} renders as □□□ -->\n    \u003Cspan id=\"eq\" data-tex=\"n \\text{ 还是 } V\">\u003C\u002Fspan>\n\n    \u003C!-- CORRECT — Chinese in HTML, math in KaTeX -->\n    \u003Cspan>判断操作对 \u003C\u002Fspan>\u003Cspan id=\"eq-n\" data-tex=\"n\">\u003C\u002Fspan>\u003Cspan> 或 \u003C\u002Fspan>\u003Cspan id=\"eq-v\" data-tex=\"V\">\u003C\u002Fspan>\u003Cspan> 的影响\u003C\u002Fspan>\n    ```\n13. **KaTeX CSS must be inlined, fonts self-hosted (字幕和公式的CSS必须内联，字体离线).** The HyperFrames compiler processes CDN `\u003Clink rel=\"stylesheet\">` tags by extracting ONLY `@font-face` rules and discarding all other CSS. This silently breaks KaTeX layout (fractions, subscripts, spacing render as flat plain text). **Never** use `\u003Clink rel=\"stylesheet\" href=\"...katex.min.css\">`. Instead: copy the shipped `assets\u002Fkatex\u002Fkatex.min.css` into `dist\u002Fkatex\u002F`, rewrite its font URLs to the **local** self-hosted path (`url(.\u002Fkatex\u002Ffonts\u002F`), and paste the full CSS as `\u003Cstyle id=\"katex-inline-css\">` in every composition and in `index.html`. Load `katex.min.js` from the local `dist\u002Fkatex\u002F` copy, **not** a CDN. See step-5 for the exact procedure.\n14. **Circuit schematic physics accuracy (电路原理图物理正确性).** When drawing circuit schematic diagrams (电路图) in SVG: (a) battery symbol — **长线 = 正极(+), 短线 = 负极(-)**, never reverse the labels; (b) current direction — conventional current flows from battery `+` terminal through external circuit to `-` terminal, all arrows must form a consistent closed loop; (c) ammeter — must be wired in **series**, current enters `+` terminal; (d) voltmeter — must be wired in **parallel** with dashed branch wires (`stroke-dasharray`), `+` terminal toward higher potential; (e) **wire segmentation (导线分段)** — wires must STOP at component terminals, never draw a continuous wire that passes through components (components are NOT decorative overlays on wires); (f) **switch must break the circuit (开关必须断路)** — incoming wire ends at switch pivot, outgoing wire starts at contact terminal, switch orientation must match wire direction (vertical wire → vertical switch), no zero-length line segments; (g) **layout distribution (布局分布)** — distribute series components across multiple sides of the rectangular loop, do NOT stack all components on one side; **an empty side of the loop may be JUST a wire (导线) — never add\u002Fduplicate a component only to \"fill\" or \"balance\" a side**; (h) **no duplicate wires** — each wire gap between components is drawn exactly once; (h2) **component inventory correctness (元件清单正确性) — 每个真实元件恰好出现一次** — the set of components must match the problem's actual circuit; single-instance instruments (**变阻器\u002F滑动变阻器, 电流表, 电压表, 开关, 电源**) must each appear EXACTLY ONCE — never invent or duplicate a component (drawing two 变阻器 in series is a physics error). Only true multiples in the problem (e.g. 两个灯泡 L₁\u002FL₂, 两个电阻 R₁\u002FR₂) may repeat. `check_circuit_inventory.py` gates this; (h3) **circuit loop MUST be closed \u002F 电源两端必须都接线 (回路闭合)** — the main series loop is a single closed path: trace it from the 电源 `+` terminal through every component back to the 电源 `-` terminal — **every gap must have a wire, and the power source's BOTH terminals must each connect to a wire**. No unwired component terminal, no **dangling wire stub** (悬空导线端点 — a wire end that lands in empty space instead of on a component terminal or another wire at a corner). In physical wiring diagrams whose wire coordinates live in a JS array (`var wires=[[\"w1\",x1,y1,x2,y2],…]`), it is easy to forget the segment that closes the left side back up to the source — **do not**. `check_circuit_closed.py` gates this; (i) **physical wiring diagram layout (实物连接图布局)** — \"连接电表\" and \"连接主回路\" operation scenes must use a **rectangular loop** layout (NOT a flat horizontal line) with ammeter IN the series loop (on the bottom return wire) and voltmeter on a visually separate dashed parallel branch; wire routing stays compact (≤100px margin beyond components); use Circuit Wiring Operation (C12) template from math-components.md, NOT Chemistry Operation Flow (C9); see circuit-schematic-guide.md Section 12; (j) **voltmeter connection wires must not protrude (电压表连接导线禁止突出)** — the `sch-voltmeter` template has NO built-in wire stubs; terminal endpoints are at the circle edge (±30 from center, not ±50); all dashed connection wires must be drawn as separate `\u003Cline>` elements from the main circuit T-junction point to the voltmeter circle edge — wires must start at the junction and end at the circle edge, never extending past either point; (k) **voltmeter branch junctions must have dots and clean routing (电压表分支点必须有圆点且路由干净)** — every T-junction where a voltmeter dashed wire meets the main circuit wire must have a filled junction dot (`\u003Ccircle r=\"4-5\">`); the dashed wire endpoint coordinates must be exactly ON the main wire (no offset); position the voltmeter so its terminals align with junction points to allow straight dashed lines; if L-shaped routing is unavoidable, use a single `\u003Cpath>` with `stroke-linejoin=\"round\"` — NEVER two separate `\u003Cline>` elements (they create gaps\u002Fprotrusions at corners). Use `sch-*` templates from [assets\u002FASSET_CATALOG.md](assets\u002FASSET_CATALOG.md) and read [references\u002Fcircuit-schematic-guide.md](references\u002Fcircuit-schematic-guide.md) for the full pre-flight checklist.\n15. **Caption formulas use KaTeX inline rendering (字幕公式必须用KaTeX渲染).** Video captions must display proper math notation — never show spoken-form Chinese like \"G M m除以R的平方\". When assembling caption `\u003Cdiv>` elements in `index.html`, replace all spoken-form math expressions from `captions.json` with `\u003Cspan class=\"cm\">LaTeX code\u003C\u002Fspan>` elements. The root `index.html` must load KaTeX JS and include a script that renders all `.cm` spans with `katex.render()` in inline mode. Use `\\tfrac` for fractions (compact, fits single-line captions). **KaTeX angle pitfall:** for degrees use `^\\circ`, for arcminutes use a plain ASCII `'` (prime), for arcseconds use `''`. **NEVER** use `\\'` or `\\\"` in KaTeX math — these are text-mode accent commands that cause the entire formula to render as red error text. See step-6 for the full template, common replacements table, KaTeX pitfalls, and examples.\n16. **Self-hosted offline fonts — Chinese MUST NOT be tofu (中文字体必须离线内嵌).** The font files are shipped with this skill under `assets\u002Ffonts\u002F` and MUST be copied into `dist\u002Fassets\u002Ffonts\u002F` (see step-5 Prerequisites). Every sub-composition file AND the root `index.html` MUST embed them via an inline `@font-face` `\u003Cstyle>` block (the exact block is in step-6 for the root and `assets\u002Fk12-scholar-font-template.css` for compositions). **Do NOT use a Google Fonts \u002F CDN `\u003Clink>`** and do NOT rely on the compiler's built-in embedding or system fonts (PingFang SC): the render sandbox is air-gapped, so a CDN font that fails to load makes ALL Chinese render as garbled boxes (乱码). Use a **CJK-first** stack everywhere — `font-family: \"Noto Sans SC\", Inter, sans-serif` — and never `Inter, sans-serif` alone. **This applies no matter HOW font-family is set** — SVG `\u003Ctext>` attr, inline `style=`, AND CSS **class rules**. The #1 miss is a small label\u002Feyebrow\u002Fbadge class (e.g. `.block-number{font-family:Inter,sans-serif}`) that the design intends for a Latin number like \"01\" but the model fills with Chinese (对称性\u002F题目所给) → those 3–4 characters render as 豆腐块 while the rest of the card is fine. Any class that could ever hold Chinese must include `\"Noto Sans SC\"`. Enforced by `scripts\u002Fcheck_cjk_font.py` (now also scans class-based font-family, not just SVG\u002Finline).\n17. **No CDN\u002Fexternal URLs at render time (渲染时禁止任何CDN\u002F外部URL).** Every `\u003Cscript src>`, `\u003Clink href>`, and CSS `url()` in compositions and `index.html` MUST use local relative paths. The AP render sandbox is **air-gapped** with no internet access — CDN URLs that load successfully on a local machine will **silently fail** on AP, breaking GSAP timelines (no animations), KaTeX rendering (raw LaTeX in captions), and font loading (tofu boxes). Specifically: (a) GSAP — load from `.\u002Fgsap\u002Fgsap.min.js` (shipped in `assets\u002Fgsap\u002F`, copied to `dist\u002Fgsap\u002F` AND `dist\u002Fcompositions\u002Fgsap\u002F` in step-5); (b) KaTeX JS — load from `.\u002Fkatex\u002Fkatex.min.js`; (c) KaTeX CSS — inline as `\u003Cstyle>`, fonts from `.\u002Fkatex\u002Ffonts\u002F`; (d) body fonts — inline `@font-face` from `.\u002Fassets\u002Ffonts\u002F`. **Never** use `cdn.jsdelivr.net`, `cdnjs.cloudflare.com`, `unpkg.com`, or any other external host in a `src` or `href` attribute. **CRITICAL: sub-compositions in `dist\u002Fcompositions\u002F` resolve `.\u002F` relative to their own directory — the assets MUST be mirrored into `dist\u002Fcompositions\u002F` (gsap\u002F, katex\u002F, assets\u002F) or all `.\u002F` paths silently 404 and GSAP\u002FKaTeX\u002Ffonts fail to load (blank scenes, no animations, tofu).** Enforced by `scripts\u002Fcheck_asset_mirror.py` in `precheck.py`.\n18. **Defensive rendering — no CSS-hidden content (防御性渲染 — CSS禁止隐藏内容).** The #1 cause of blank videos is CSS `opacity: 0` on `.scene-content` or content elements, combined with a JS error that prevents GSAP from revealing them. **Rules:** (a) **Never** set `opacity: 0`, `visibility: hidden`, or `display: none` on any content element in CSS — GSAP handles the initial hidden state dynamically via `autoAlpha: 0` in `fromTo()`; (b) Wrap the **entire** GSAP timeline construction in a `try-catch` block — if any tween fails, the timeline still registers and content stays visible; (c) Each `katex.render()` call must have its **own** `try-catch` — one bad LaTeX string must not crash the entire composition; (d) Always pass `throwOnError: false` to `katex.render()`. See step-5 \"Defensive Script Pattern\" for the complete code template.\n19. **Reproducibility — stable structure every run (稳定复现 — 结构固定).** The same problem must yield the same well-formed video every time; the biggest source of run-to-run quality swing is the BUILD step deviating from the plan. **Rules:** (a) **Fixed output path** — scaffold and render in exactly `.\u002Fdist` at the workspace root; never nest it (`math-tutorial-output\u002Fdist` etc.) — the renderer and gates assume `.\u002Fdist`. (b) **One composition per storyboard scene** — build exactly the scenes listed in `STORYBOARD.md`, one concept\u002Fray\u002Fstep per scene; never merge several planned scenes into one file, never skip a planned scene. `index.html` must wire ≥ the planned scene count. (c) **Every scene self-contained & complete** — each composition draws its full content (e.g. an optics scene MUST include the horizontal principal axis through O with 2F\u002FF\u002FO\u002FF′\u002F2F′ marked) and registers `window.__timelines[...]`. (d) **Determinism** — no `Math.random()` \u002F `Date.now()` \u002F async timeline construction (see rule #7). (e) These are enforced deterministically by `$EDU_SKILL_ROOT\u002Fscripts\u002Fcheck_scene_coverage.py` and `$EDU_SKILL_ROOT\u002Fscripts\u002Fcheck_no_hidden_content.py` inside `$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py` — `precheck.py dist` must print `ALL CHECKS PASSED` before rendering.\n20. **Root MUST reference scenes via `data-composition-src` (根合成引用名固定 — 否则全白).** In `dist\u002Findex.html`, every scene clip MUST use the EXACT attribute **`data-composition-src=\"compositions\u002Fscene-X.html\"`** — never `data-src`, `src`, or a missing src. HyperFrames only loads\u002Fcompiles an external scene when it sees `data-composition-src`; any other name means **no scene loads and the whole video renders white** (only the caption bar shows). Also: each clip's `data-composition-id` must match that scene's registered `window.__timelines[\"\u003Cid>\"]`, and scenes are laid out directly inside the root composition div (do NOT wrap them in an extra `data-composition-id=\"mt-root\"` container). Enforced by `scripts\u002Fcheck_root_compositions.py` in `precheck.py`.\n21. **几何图形坐标必须数学精确 (Geometry SVG coordinates must be mathematically exact).** When drawing geometric figures in SVG (Geometry Canvas scenes), all vertex coordinates MUST be computed from the problem's mathematical constraints (perpendicularity, parallelism, similarity ratios, length ratios, etc.) — **never placed by visual estimation**. Mandatory steps: (a) solve the problem's equations to get exact proportional relationships between all segments before writing any SVG; (b) choose a base unit in the SVG viewBox, then compute every point's coordinates from the ratios; (c) **verify perpendicularity** — if two lines are perpendicular, compute the dot product of their direction vectors and confirm it equals 0; (d) **verify length ratios** — compute Euclidean distances and confirm they match the problem's given ratios; (e) write a `\u003C!-- GEOMETRY VERIFICATION -->` block above the SVG with a `POINTS:` line declaring all coordinates and `ASSERT` lines for every problem constraint (parallel, perpendicular, midpoint, on_line, on_segment, intersection, ratio, collinear) — the pre-render gate `scripts\u002Fcheck_geometry_verification.py dist` mathematically verifies every assertion against the declared coordinates and exits 0; (f) **right angle marks (直角符号)** must be drawn as small L-shaped squares (not V-shapes or arcs), with edges computed from the actual line direction unit vectors — see step-5-build-components.md \"Geometry Coordinate Accuracy\" for the verification block format, assertion types, and construction algorithms.\n22. **角度弧线必须用叉积确定扫描方向 (Angle arcs must use cross-product to determine sweep direction).** When marking a non-right angle at vertex V with arms pointing to adjacent vertices P1 and P2 in SVG, the arc MUST sweep through the **interior** of the angle being marked. The sweep direction is determined by the cross product of the two arm vectors — never by visual estimation or guesswork. Algorithm:\n    ```\n    \u002F\u002F 1. Direction vectors from vertex V to each arm endpoint\n    dx1 = P1.x - V.x;  dy1 = P1.y - V.y;\n    dx2 = P2.x - V.x;  dy2 = P2.y - V.y;\n\n    \u002F\u002F 2. Cross product (in SVG screen coordinates, y-down)\n    cross = dx1 * dy2 - dy1 * dx2;\n\n    \u002F\u002F 3. Arc start\u002Fend points on each arm at radius distance\n    angle1 = Math.atan2(dy1, dx1);\n    angle2 = Math.atan2(dy2, dx2);\n    startX = V.x + R * Math.cos(angle1);\n    startY = V.y + R * Math.sin(angle1);\n    endX   = V.x + R * Math.cos(angle2);\n    endY   = V.y + R * Math.sin(angle2);\n\n    \u002F\u002F 4. Sweep flag: cross > 0 → interior is CW → sweep=1\n    \u002F\u002F                cross \u003C 0 → interior is CCW → sweep=0\n    sweepFlag = cross > 0 ? 1 : 0;\n\n    \u002F\u002F 5. SVG path (large-arc-flag = 0 for angles \u003C 180°)\n    d = `M ${startX},${startY} A ${R},${R} 0 0,${sweepFlag} ${endX},${endY}`;\n    ```\n    **Worked example — rhombus with ∠A = 60°, ∠B = 120°:**\n    ```\n    \u002F\u002F Vertices: A(200,400) B(400,180) C(600,400) D(400,620), R=30\n\n    \u002F\u002F ∠A = 60° — arms AB and AD\n    dx1=200, dy1=-220; dx2=200, dy2=220;\n    cross = 200*220 - (-220)*200 = 88000 > 0 → sweep=1\n    \u002F\u002F Arc sweeps CW through interior (rightward between AB and AD) ✅\n\n    \u002F\u002F ∠B = 120° — arms BA and BC\n    dx1=-200, dy1=220; dx2=200, dy2=220;\n    cross = (-200)*220 - 220*200 = -88000 \u003C 0 → sweep=0\n    \u002F\u002F Arc sweeps CCW through interior (downward between BA and BC) ✅\n    ```\n    **Common mistakes:** (a) Hardcoding sweep-flag=1 or sweep-flag=0 without computing cross product — causes arcs to appear on the exterior (reflex) side of the angle; (b) Drawing the arc between the wrong pair of arms (e.g., marking ∠ABC but using arms BA and BD); (c) Using a right-angle L-shaped mark for non-90° angles; (d) Forgetting that SVG y-axis points down, which reverses the visual rotation direction. **Always verify:** visually, the arc must curve TOWARD the interior of the polygon, not away from it.\n23. **坐标分隔符只用逗号，禁止分号 (Coordinate separators: comma only, never semicolon).** When writing coordinate pairs, point notation, or ordered tuples in KaTeX (e.g., vertex coordinates, intersection points), use **only a comma** as the separator between components — never a semicolon, and never both. Add a KaTeX thin space `\\,` after the comma for readability. **JS string escaping pitfall:** in a JavaScript string passed to `katex.render()`, write `\\\\,` (double backslash) so KaTeX receives `\\,`. Writing `\\;` in a JS string does NOT produce a KaTeX thick space — JS treats the unknown escape `\\;` as a literal semicolon character `;`, so KaTeX renders a visible `,;` (comma followed by semicolon). This is a confirmed rendering bug. Correct examples:\n    ```\n    \u002F\u002F In JS strings (double-escaped for JS → KaTeX):\n    katex.render(\"(1,\\\\, b-1)\", el);          \u002F\u002F ✅ renders (1, b−1)\n    katex.render(\"(x_0,\\\\, y_0)\", el);        \u002F\u002F ✅ renders (x₀, y₀)\n\n    \u002F\u002F WRONG:\n    katex.render(\"(1,\\; b-1)\", el);           \u002F\u002F ❌ renders (1,; b−1) — JS turns \\; into literal ;\n    katex.render(\"(1,\\\\; b-1)\", el);          \u002F\u002F ❌ renders (1, b−1) with excessive space\n\n    \u002F\u002F In HTML (e.g., \u003Cspan class=\"cm\"> for caption KaTeX):\n    \u003Cspan class=\"cm\">(1,\\, b-1)\u003C\u002Fspan>        \u002F\u002F ✅\n    \u003Cspan class=\"cm\">(1,\\; b-1)\u003C\u002Fspan>        \u002F\u002F ❌ overly wide space, inconsistent\n    ```\n    General rule: coordinates are `(a,\\, b)` in LaTeX, `(a,\\\\, b)` in JS strings. No semicolons anywhere in coordinate notation — this is Chinese math convention (中国数学用逗号分隔坐标，不用分号).\n24. **Sub-composition files must be HTML fragments, NOT full documents (子合成文件必须是HTML片段，不能是完整HTML文档).** Every file in `dist\u002Fcompositions\u002F*.html` must be an HTML fragment — a root `\u003Cdiv data-composition-id=\"...\">` with inline `\u003Cstyle>` and `\u003Cscript>` blocks. **NEVER** wrap sub-compositions in `\u003C!doctype html>`, `\u003Chtml>`, `\u003Chead>`, or `\u003Cbody>` tags. HyperFrames loads sub-compositions by injecting their content into the parent document; a full HTML document wrapper prevents the renderer from properly executing GSAP scripts inside the sub-composition, causing all content elements to remain invisible (blank white panels with only background texture showing — audio and captions play normally, making the bug deceptive). Only the root `index.html` is a full HTML document. Enforced by `scripts\u002Fcheck_composition_format.py` in `precheck.py`.\n25. **简单符号优先用中文或HTML实体，避免不必要的KaTeX (Prefer Chinese\u002FHTML entities over KaTeX for simple math symbols).** KaTeX rendering in JS strings requires double-escaped backslashes (`\\\\leq` not `\\leq`); a single-backslash error is **silent** — JS eats `\\l`→`l`, `\\a`→`a`, `\\c`→`c`, producing garbled text like \"OP'leq1\" or \"angleMPNleq90^circ\" instead of proper symbols. **To avoid this fragile pipeline, prefer plain HTML or Chinese text whenever the expression does NOT need math layout features** (fractions, roots, matrices, complex sub\u002Fsuperscripts). Decision table:\n    | Expression type | Display goal | Method | Code |\n    |---|---|---|---|\n    | Simple comparison | OP' ≤ 1 | HTML entity | `\u003Cspan>OP' ≤ 1\u003C\u002Fspan>` or `OP' &le; 1` |\n    | Angle expression | 角MPN ≤ 90° | Chinese + entity | `\u003Cspan>角MPN ≤ 90°\u003C\u002Fspan>` |\n    | Degree symbol | 90° | HTML entity | `90&deg;` or `90°` |\n    | Multiplication | 3 × 4 | HTML entity | `3 &times; 4` |\n    | Simple inequality | x ≥ 5 | HTML entity | `\u003Cspan>x ≥ 5\u003C\u002Fspan>` |\n    | Fraction layout | ½ | **KaTeX** | `katex.render(\"\\\\\\\\frac{1}{2}\", ...)` |\n    | Complex formula | quadratic | **KaTeX** | `katex.render(\"\\\\\\\\frac{-b\\\\\\\\pm\\\\\\\\sqrt{b^2-4ac}}{2a}\", ...)` |\n    | Root expression | √(a²+b²) | **KaTeX** | `katex.render(\"\\\\\\\\sqrt{a^2+b^2}\", ...)` |\n    | Subscript variable | x₁, y₂ | HTML `\u003Csub>` | `x\u003Csub>1\u003C\u002Fsub>`, `y\u003Csub>2\u003C\u002Fsub>` |\n    | Greek letter | α, β, θ | Unicode char | `\u003Cspan>α\u003C\u002Fspan>` — NEVER write \"alpha\" |\n    Safe HTML entities: `≤` (`&le;`), `≥` (`&ge;`), `≠` (`&ne;`), `°` (`&deg;`), `×` (`&times;`), `÷` (`&divide;`), `±` (`&plusmn;`), `²` (`&sup2;`), `³` (`&sup3;`).\n    Safe Unicode Greek letters (in Inter font): α, β, γ, δ, ε, θ, λ, μ, π, φ, ω — use directly, never write English names.\n    Chinese replacements for geometric symbols: \"角\" (∠), \"三角形\" (△), \"垂直于\" (⊥), \"平行于\" (∥).\n    **Rule of thumb: if the expression is a simple comparison, a standalone symbol, or a Chinese-friendly geometric term, use HTML\u002FChinese. If it has fractions, roots, summations, or nested structure, use KaTeX.**\n26. **SVG 坐标轴箭头必须用 orient=\"auto\" + 朝右三角形 (Axis arrows: always use a single right-pointing marker with orient=\"auto\").** When drawing coordinate axes in SVG, define ONE arrowhead marker with its polygon pointing in the **+x direction** of the marker's local coordinate system, and set `orient=\"auto\"`. This same marker works for ALL axis directions (x-axis, y-axis, or any angle) because `orient=\"auto\"` rotates the marker to match the stroke direction at the endpoint. **NEVER define a separate \"upward\" marker with a polygon pre-rotated to point up** — `orient=\"auto\"` will rotate it an additional -90°, making the arrowhead point LEFT instead of UP. Correct pattern:\n    ```html\n    \u003Cdefs>\n      \u003C!-- ONE marker for ALL axes — polygon points right (+x), orient=\"auto\" rotates it -->\n      \u003Cmarker id=\"axis-arrow\" markerWidth=\"10\" markerHeight=\"7\" refX=\"10\" refY=\"3.5\" orient=\"auto\">\n        \u003Cpolygon points=\"0 0, 10 3.5, 0 7\" fill=\"#0f172a\"\u002F>\n      \u003C\u002Fmarker>\n    \u003C\u002Fdefs>\n    \u003C!-- x-axis: line goes right → arrow points right ✅ -->\n    \u003Cline x1=\"50\" y1=\"400\" x2=\"570\" y2=\"400\" marker-end=\"url(#axis-arrow)\"\u002F>\n    \u003C!-- y-axis: line goes up → orient=\"auto\" rotates arrow to point up ✅ -->\n    \u003Cline x1=\"300\" y1=\"470\" x2=\"300\" y2=\"30\" marker-end=\"url(#axis-arrow)\"\u002F>\n    ```\n    **Common mistake (y-axis arrow points LEFT):**\n    ```html\n    \u003C!-- ❌ WRONG — polygon already points up, orient=\"auto\" rotates it -90° more → points LEFT -->\n    \u003Cmarker id=\"arrowU\" orient=\"auto\">\u003Cpolygon points=\"0 10, 3.5 0, 7 10\"\u002F>\u003C\u002Fmarker>\n    ```\n    **Why:** `orient=\"auto\"` aligns the marker's local +x axis with the line's tangent direction. For a line from (300,470) to (300,30), the tangent is (0,−1) = upward, so the local +x rotates −90°. A right-pointing triangle (+x direction) correctly becomes upward. An already-upward triangle (−y direction) gets rotated to point left.\n27. **受力箭头\u002F坐标轴禁止加辉光滤镜 (NEVER apply a glow\u002Fblur `filter` to an axis-aligned `\u003Cline>`).** A perfectly horizontal or vertical `\u003Cline>` — every force arrow (重力 G↓ \u002F 支持力 N↑ \u002F 摩擦力 f \u002F 拉力), coordinate axis, horizontal light ray, or wire — has a **degenerate bounding box** (zero height when horizontal, zero width when vertical). SVG filters default to `filterUnits=\"objectBoundingBox\"` with a region of `-10%…120%` of the bbox, so a zero dimension makes the **filter region zero-area → the line AND its `marker-end` arrowhead render completely invisible.** The `\u003Ctext>` label carries no filter, so it still shows — producing the deceptive symptom **\"受力示意图里只有力的字(G\u002FN\u002Fv)，没有力的箭头线条.\"** This bug hides on slanted rays (a diagonal line has a nonzero-area bbox and survives), so it only surfaces on vertical\u002Fhorizontal force arrows. **Fix:** give force arrows a solid `stroke` (width 4–6) + `marker-end` and **NO `filter`** — a force arrow reads perfectly without a glow. If a glow on straight strokes is truly needed, make the filter absolute so it ignores the bbox: `\u003Cfilter id=\"fGlow\" filterUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"\u003CviewBoxW>\" height=\"\u003CviewBoxH>\">…\u003C\u002Ffilter>`. Glow filters remain safe on circles\u002Fparticles\u002Fions\u002Ftext (non-degenerate bbox). Enforced by `scripts\u002Fcheck_svg_filter_bbox.py` in `precheck.py`.\n28. **SVG 标签禁止相互重叠 (SVG `\u003Ctext>` labels must NOT overlap each other).** SVG `\u003Ctext>` is positioned by hand (absolute `x`\u002F`y` in viewBox units), so labels crammed around a shared point or along a short line collide and the text mashes together (文字重叠). The classic failure is a pulley\u002Flever diagram that places the fulcrum label `支点 O` and the two arm labels `动力臂 r` \u002F `阻力臂 r` on the SAME horizontal diameter through the circle center — three centered labels in ~90px of room overlap into \"动力臂 支点O 阻力臂 r\". This class of overlap is invisible to the other layout gates (`check_scene_overflow.py` ignores SVG user coordinates; `check_scene_layout.py` only checks the DOM wrapper). **Rules:** (a) **never stack multiple labels on a shared point or a short segment** — give each label its own side with a clear gap of **≥ ~1 label-height** (≥ the label's `font-size` in viewBox units) between neighbouring boxes; (b) for a center\u002Ffulcrum + two arm labels, put the **center label ABOVE the line** and the **arm labels BELOW it** (or vice-versa) so their vertical bands don't collide; (c) **anchor arm\u002Fsegment labels `text-anchor=\"end\"` (left side) \u002F `\"start\"` (right side)** and push them **outward past the shape edge** rather than toward the crowded center; (d) offset a label **perpendicular to the line\u002Farm it names**, not along it; (e) when two point labels sit on the same axis (e.g. `A(-3,0)` and `E(-5,0)`), stagger their `y` or shorten to bare letters so their boxes clear. Do NOT let a point label sit on top of an axis tick number — move the tick or drop the redundant one. Enforced by `scripts\u002Fcheck_svg_label_overlap.py` in `precheck.py` (conservative: flags only substantial 2-D overlap, accounts for `\u003Cg transform=\"translate\">` and GSAP-repositioned elements).\n29. **受力\u002F矢量箭头必须正确 (SVG force\u002Fvector arrows — fixed-size head, full-length line).** Hand-rolled SVG arrows (`\u003Cline marker-end>`) fail in three severe ways at once, as seen in a 蹦床受力 scene (giant sideways arrowheads with no shaft): (a) **箭头太大** — a `\u003Cmarker>` with no `markerUnits` defaults to `markerUnits=\"strokeWidth\"`, so the arrowhead is multiplied by the (thick, 6–8) force stroke → a ~72–96px triangle. **Fix:** `\u003Cmarker markerUnits=\"userSpaceOnUse\" markerWidth=\"14\" markerHeight=\"14\" refX=… refY=… orient=\"auto\">` sized in absolute px. (b) **方向不对 + (c) 线没画出来** — drawing the `\u003Cline>` collapsed to a point (`x1==x2 && y1==y2`) and growing it via GSAP `attr:{y2}`: a zero-length line has no shaft, and `orient=\"auto\"` on it is undefined so the head points sideways; if the attr tween doesn't seek at render, the shaft never appears. **Fix:** draw the line at its **full, correct length** (so orient is right and the shaft always exists) and reveal it with `stroke-dashoffset` draw-on — never animate a marker line's length from zero. **BEST:** reuse the prebuilt CSS `force-arrow` component (`assets\u002Fcomponents\u002Fmechanics\u002Fforce-arrow.html`) — fixed head, rotate for direction, `--arrow-length` for magnitude. Enforced by `scripts\u002Fcheck_svg_arrow.py` in `precheck.py`.\n30. **图形标签禁止压在图形上 (SVG labels must NOT overlap the drawing — 文字不压线不压点).** A point\u002Fvertex\u002Fcurve letter (A\u002FB\u002FO\u002FF\u002FM…) must never sit ON a line, axis, symmetry line, curve, or vertex dot — it must be **offset OUTWARD, away from the figure, by ~18–24 units**. Classic failure: a point `M` on the symmetry axis gets its label placed straddling that axis's x, so the dashed axis line runs straight through the \"M\". **Rules:** (a) place a small dot at the point and the **letter ~20u beside it, on the side away from the shape centre** (never at the exact point coordinate); (b) a point on the **x-axis** → label ~22u **below\u002Fabove** the axis, not on it; the **origin `O`** → offset into a quadrant (`x−14, y+20`); (c) a label near a **vertical\u002Fsymmetry line** → anchor `end` and push left (or `start`\u002Fright) so the stroke clears the glyphs; (d) a **curve** label sits in the empty area beside the curve. Nudging a label a little to clear the figure is expected and encouraged. This is distinct from label-vs-label overlap (Rule #28) — here the collision is label-vs-geometry. Enforced by `scripts\u002Fcheck_svg_label_on_figure.py` in `precheck.py`.\n31. **关系图\u002F流程图节点必须打组、箭头不压框、图形内白字必显 (SVG node-and-connector diagrams).** When a scene draws a food chain \u002F feedback loop \u002F flow diagram from SVG boxes + arrows + in-shape labels, three failures ruin it (seen in a 生态平衡 case: white `负反馈` invisible, arrows poking into the 方框). **Rules:** (a) **a label inside a shape MUST be in the SAME `\u003Cg>` as that shape** — `\u003Cg>\u003Ccircle\u002F>\u003Ctext\u002F>\u003C\u002Fg>`, never independent siblings; otherwise a GSAP `scale`\u002Fpulse\u002F`back.out` overshoot or `svgOrigin` residue moves the shape but not the text, so the label **slides off the shape onto the panel and disappears** (the \"白色文字没显示\" bug). Animate\u002Fposition the group, not the shape alone. (b) **white SVG `\u003Ctext>` is allowed ONLY inside a dark shape that contains it** (white-on-light is invisible; `#ffffff` text is otherwise forbidden — see Rule on light-theme colors), and it must be grouped with that shape per (a); size the shape so the text fits with margin. (c) **arrow\u002Fconnector endpoints stop ≥ ~10u before the target box edge** (account for `markerWidth`) — never end a `marker-end` line\u002Fpath inside or flush against a `\u003Crect>` node, or the arrowhead hides under \u002F pokes into the 方框. (d) **edge labels (\"取食\"\u002F\"捕食\") go in the gap between boxes with clearance**, not against a box; space boxes so connector + label + clearances fit. Enforced by `scripts\u002Fcheck_svg_node_graph.py` in `precheck.py`.\n32. **SVG 高度必须有界，禁止 `width:100%;height:auto` 撑爆场景 (SVG height must be bounded — never let width drive an unbounded height).** A content `\u003Csvg>` styled `width:100%; height:auto` sizes its width to the panel (~1380px in a full-width panel) and then, because `height:auto`, sets its **height from the viewBox aspect ratio** — a near-square viewBox like `0 0 1000 760` becomes **~1050px tall**, far past the ~860px usable height (1080 − top padding − 180px caption safe zone). The SVG overflows the frame and the scene looks like it **\"suddenly got bigger\" (场景突然变大)** than the earlier text scenes. This is invisible to the literal-px overflow gate (there is no `px > frame`). **Rules:** (a) **never** write `width:100%; height:auto` on a content SVG with a tall\u002Fnear-square viewBox (and equally never `width:100%; aspect-ratio:1` — a CSS `aspect-ratio` drives the height from the width just the same); (b) **bound the height** — either add `max-height:\u003Cusable>px` (e.g. `max-height:760px`) so the SVG letterboxes, or size it **by height** (`height:100%` inside a bounded flex\u002Fgrid box) and let `preserveAspectRatio=\"xMidYMid meet\"` shrink the width to fit; (c) alternatively choose a **viewBox aspect ratio that matches the available box** (wide\u002Flandscape, ratio ≲ 0.5) so `width:100%` keeps the height under ~820px. The origin of the \"后面的渲染场景突然变大\" bug was exactly a 图象验证 (graph) scene with `#svg{width:100%;height:auto}` on a `1000×760` viewBox. Enforced deterministically by `scripts\u002Fcheck_svg_height_bound.py` (browserless) and measured at runtime by `scripts\u002Fcheck_scene_fit.py`, both in `precheck.py`.\n33. **禁止对带定位 `transform` 属性的 SVG 元素补间 x\u002Fy\u002Frotation\u002Fscale (never GSAP-tween a transform prop on a transform-positioned SVG element — 否则物体飞出画面).** An SVG element placed with a hard-coded transform attribute, e.g. `\u003Cg transform=\"translate(390,364) rotate(-30)\">` (a block on an incline, a rotated arrow, a positioned symbol), must **NOT** be the target of a GSAP tween that animates a transform shorthand (`x`, `y`, `rotation`, `rotationX\u002FY\u002FZ`, `scale`, `scaleX\u002FY`, `xPercent`, `yPercent`, `skewX\u002FY`). GSAP parses the element's existing `transform` into its own model, then the tween **overwrites** it — e.g. `fromTo(\"g[transform]\", {y:10}, {y:0})` drives translateY from its real 364 down to 0, so the element **jumps ~364px off its position and flies out of frame** (物体飞出画面). The classic failure is a broad selector like `g[transform]` or a bare `g`\u002F`rect` selector that happens to hit a positioned group. **Rules:** (a) on a transform-positioned SVG element, animate **ONLY** `opacity`\u002F`autoAlpha` (never x\u002Fy\u002Frotation\u002Fscale); (b) to also slide\u002Fscale it, **NEST** — keep the positioning transform on a **static outer `\u003Cg transform=...>`** and put the fade\u002Fslide tween on an **inner `\u003Cg>`** (or wrapper); (c) if you truly must animate its transform, use `attr:{transform:'translate(...) rotate(...)'}` with the **full** transform written out at both ends, never a bare `x`\u002F`y` shorthand; (d) never target positioned groups with a broad `g[transform]` \u002F bare-tag selector. Animating x\u002Fy\u002Fscale on elements **without** a transform attribute (plain circles, HTML panels positioned by CSS) is fine. **(e) 对 SVG 元素做 `scale`\u002F`rotation` 缩放\u002F旋转时，绝不要用 px 单位的 `transformOrigin`（如 `transformOrigin:\"470px 270px\"`）——在 SVG 上它按元素包围盒本地坐标换算，对一个子元素坐标远离 SVG 原点的 `\u003Cg>` 缩放会附带一个很大的平移量，把元素甩飞\u002F错位（即使该 `\u003Cg>` 没有 transform 属性、纯靠子元素绝对坐标定位，也会飞——这正是\"变阻器缩短动画把整个 R₁ 甩到画面外\"的真实事故）。改用 GSAP 专给 SVG 的 `svgOrigin:\"x y\"`（用户坐标系，如 `svgOrigin:\"470 270\"`），或把元素套进 `\u003Cg transform=\"translate(...)\">` 再对内层缩放。`transformOrigin` 用百分比（如 `\"50% 50%\"`）在 SVG 上是安全的。** Enforced by `scripts\u002Fcheck_svg_transform_anim.py` in `precheck.py`.\n\n## Golden Examples (Few-Shot References)\n\nBefore building compositions for a new problem, **read the matching golden example** to see the complete pipeline (PROBLEM → ANALYSIS → SCRIPT → STORYBOARD → HTML compositions → index.html) executed at top quality. These are condensed from real high-scoring videos — no binary files, just the essential pipeline artifacts and representative composition code.\n\n| Example | Problem Type | When to Read |\n|---|---|---|\n| [references\u002Fgolden-example-geometry.md](references\u002Fgolden-example-geometry.md) | Geometry proof (旋转、等边三角形、垂直平分线) | **Any geometry proof problem** — shows coordinate computation, GEOMETRY VERIFICATION blocks, split layout (SVG + note blocks), progressive draw-on animations synced to narration, proof-step overlay patterns |\n| [references\u002Fgolden-example-interactive-transform.md](references\u002Fgolden-example-interactive-transform.md) | **Interactive HTML explorer (交互式网页, 不是视频)** | **When the user asks for an interactive page \u002F 交互式界面 \u002F 让用户拖动体验 (sliders\u002Fcontrols that update a graph AND the equation live), instead of an MP4.** Ships a complete offline `index.html` (Canvas graph + KaTeX live equation + dropdown\u002Fsliders\u002Fcheckboxes\u002Freset, Aurora theme) plus the 6 rules to reproduce it exactly. Skip the Step 0–6 video pipeline for this. |\n\n> ⚙️ **Two output modalities.** Most requests → a narrated **MP4** via the Step 0–6 pipeline (use the\n> geometry \u002F process-animation examples above). But when the user explicitly wants an **interactive\n> web page** (拖动参数、实时联动，不是视频), switch to the **interactive** golden example above — same\n> Aurora look & offline assets, but the deliverable is a self-contained `index.html` opened by\n> double-click, not a rendered video.\n\n34. **KaTeX 必须在合成加载时【同步】渲染，并按 `id`\u002F`data-composition-id` 作用域选择；禁止无 `id` 的 `#合成id` 选择器，禁止用 setTimeout 延迟渲染 (render KaTeX synchronously at load, scoped correctly; never a `#composition-id` selector without a matching `id`, never defer with setTimeout).** 已确认两类\"公式空白\"坑（画面空白，选项\u002F步骤\u002F公式看不到）：**(1) 缺根 `id`（确定性、最常见）** — 根 div 是 `\u003Cdiv data-composition-id=\"mt-x\">`（`data-composition-id` 是 data 属性，不是 DOM id），但渲染循环用 `document.querySelectorAll(\"#mt-x [data-tex]\")`；`#mt-x` **匹配 0 个** → 不渲染 → 公式全空。**修复**：给根 div 同时加 `id=\"mt-x\"`（保留 `data-composition-id`），或用 `[data-composition-id=\"mt-x\"]` \u002F `document.currentScript.closest(\"[data-composition-id]\")` 作 id 无关作用域。由 `scripts\u002Fcheck_composition_root_id.py` 拦截（q067 弧长公式只显示\"=\"就是此因，加 `id` 后重渲已修复）。**(2) 必须同步渲染，切勿延迟** — HyperFrames 渲染器按 seek GSAP 时间轴的方式**确定性抓帧**，**不保证执行 `setTimeout`\u002F`requestAnimationFrame` 等异步回调**；因此 KaTeX 要在合成内联 `\u003Cscript>` **加载时同步渲染**（此时 index.html 已在 `\u003Chead>` 同步载入 katex，全局可用），**不要**把 `katex.render` 包进 `setTimeout`\u002F轮询，也不要依赖 `onload`\u002F末尾追加脚本（追加在根 `\u003C\u002Fdiv>` 之后的 `\u003Cscript>` 注入时会被丢弃、根本不执行）。优先用静态 `data-tex` 属性 + 同步 `querySelectorAll` 渲染循环（见 step-5 Rule 4）。**已知未解边缘情形**：个别场景在 `hyperframes validate`\u002F单独打开时公式正常、但最终 MP4 里仍空白（该场景的命令式 KaTeX 未被抓帧保留，如 q062 的标注框）——遇到务必以**渲染出的 MP4** 为准复核，必要时重写该场景标记。\n\n## References Index\n\n| File | When to Read |\n|---|---|\n| [design-system.md](design-system.md) | Before writing any HTML |\n| [references\u002Fgolden-example-geometry.md](references\u002Fgolden-example-geometry.md) | **Few-shot example** — geometry proof pipeline (PROBLEM → HTML compositions) |\n| [references\u002Fgolden-example-interactive-transform.md](references\u002Fgolden-example-interactive-transform.md) | **Few-shot example** — interactive HTML explorer (交互式网页, 非视频): live graph + live equation via sliders, offline self-contained; full reference index.html + 6 reproduce-rules |\n| [references\u002Fexample-process-animation.md](references\u002Fexample-process-animation.md) | **Few-shot example** — process\u002Fmotion animation (生物过程\u002F受力\u002F波动\u002F演示): split-and-move, connectors that shorten & pull (纺锤丝\u002F绳\u002F矢量), live count-up, stepped chart; 2 complete reference scenes |\n| [references\u002Fstep-0-image-input.md](references\u002Fstep-0-image-input.md) | Step 0 — when input is image_assets\u002F |\n| [references\u002Fmath-components.md](references\u002Fmath-components.md) | When building scene compositions (Step 5) |\n| [references\u002Fstep-1-problem-analysis.md](references\u002Fstep-1-problem-analysis.md) | Step 1 |\n| [references\u002Fstep-2-teaching-script.md](references\u002Fstep-2-teaching-script.md) | Step 2 |\n| [references\u002Fstep-3-voice-generation.md](references\u002Fstep-3-voice-generation.md) | Step 3 |\n| [references\u002Fstep-4-storyboard.md](references\u002Fstep-4-storyboard.md) | Step 4 |\n| [references\u002Fstep-5-build-components.md](references\u002Fstep-5-build-components.md) | Step 5 |\n| [assets\u002FASSET_CATALOG.md](assets\u002FASSET_CATALOG.md) | 生成视觉对象前必读，83 个预置组件索引 |\n| [references\u002Fcircuit-schematic-guide.md](references\u002Fcircuit-schematic-guide.md) | 电路原理图（电路图）绘制规范 — 电池极性、电流方向、电表接法 |\n| [references\u002Fgeometry-construction-guide.md](references\u002Fgeometry-construction-guide.md) | 几何图形构造 — 坐标计算模式、角度弧线、三角形\u002F四边形\u002F圆\u002F旋转\u002F翻折实例 |\n| [references\u002Fstep-6-compose-render.md](references\u002Fstep-6-compose-render.md) | Step 6 |\n",{"data":38,"body":39},{"name":4,"description":6},{"type":40,"children":41},"root",[42,51,66,73,101,500,544,570,701,707,976,982,992,1013,1019,1025,1034,1046,1051,1057,1066,1071,1076,1082,1128,1147,1152,1158,1175,1210,1215,1221,1340,1467,8731],{"type":43,"tag":44,"props":45,"children":47},"element","h1",{"id":46},"math-tutorial-video-generator",[48],{"type":49,"value":50},"text","Math Tutorial Video Generator",{"type":43,"tag":52,"props":53,"children":54},"p",{},[55,57,64],{"type":49,"value":56},"Transforms a math problem into a step-by-step Chinese-language video tutorial. Teaching components — equations, geometric figures, solution steps — are rendered as realistic objects: solid opaque panels (glass-look via borders + layered shadow, NO ",{"type":43,"tag":58,"props":59,"children":61},"code",{"className":60},[],[62],{"type":49,"value":63},"backdrop-filter",{"type":49,"value":65},", NO translucency), 3D metallic cards, animated SVG constructions, on themed backgrounds with animated gradient orbs and smooth ambient effects. The default theme is \"Aurora Scholar\" (blue wave texture + indigo\u002Fviolet\u002Fcyan orbs); 4 alternative light themes are available (清雅湖蓝, 柔紫轻盈, 薄荷清新, 暖黄纸感) for visual variety across different problem types — see the Background Theme Catalog in design-system.md.",{"type":43,"tag":67,"props":68,"children":70},"h2",{"id":69},"prerequisites-环境准备-开工前必查",[71],{"type":49,"value":72},"Prerequisites (环境准备 — 开工前必查)",{"type":43,"tag":52,"props":74,"children":75},{},[76,78,84,86,91,93,99],{"type":49,"value":77},"This is a ",{"type":43,"tag":79,"props":80,"children":81},"strong",{},[82],{"type":49,"value":83},"skill-only",{"type":49,"value":85}," capability (no MCP server), so its runtime dependencies are ",{"type":43,"tag":79,"props":87,"children":88},{},[89],{"type":49,"value":90},"NOT",{"type":49,"value":92}," auto-installed by ",{"type":43,"tag":58,"props":94,"children":96},{"className":95},[],[97],{"type":49,"value":98},"uvx",{"type":49,"value":100},". Verify ALL of the following before Step 0 — a missing one silently breaks a later step:",{"type":43,"tag":102,"props":103,"children":104},"table",{},[105,129],{"type":43,"tag":106,"props":107,"children":108},"thead",{},[109],{"type":43,"tag":110,"props":111,"children":112},"tr",{},[113,119,124],{"type":43,"tag":114,"props":115,"children":116},"th",{},[117],{"type":49,"value":118},"Dependency",{"type":43,"tag":114,"props":120,"children":121},{},[122],{"type":49,"value":123},"Needed for",{"type":43,"tag":114,"props":125,"children":126},{},[127],{"type":49,"value":128},"Install \u002F check",{"type":43,"tag":130,"props":131,"children":132},"tbody",{},[133,169,230,302,327,378,418],{"type":43,"tag":110,"props":134,"children":135},{},[136,145,158],{"type":43,"tag":137,"props":138,"children":139},"td",{},[140],{"type":43,"tag":79,"props":141,"children":142},{},[143],{"type":49,"value":144},"Node.js + npm\u002Fnpx",{"type":43,"tag":137,"props":146,"children":147},{},[148,150,156],{"type":49,"value":149},"scaffold + render (",{"type":43,"tag":58,"props":151,"children":153},{"className":152},[],[154],{"type":49,"value":155},"npx hyperframes",{"type":49,"value":157},")",{"type":43,"tag":137,"props":159,"children":160},{},[161,167],{"type":43,"tag":58,"props":162,"children":164},{"className":163},[],[165],{"type":49,"value":166},"node -v",{"type":49,"value":168}," (≥18)",{"type":43,"tag":110,"props":170,"children":171},{},[172,180,211],{"type":43,"tag":137,"props":173,"children":174},{},[175],{"type":43,"tag":79,"props":176,"children":177},{},[178],{"type":49,"value":179},"hyperframes CLI",{"type":43,"tag":137,"props":181,"children":182},{},[183,189,191,197,198,204,205],{"type":43,"tag":58,"props":184,"children":186},{"className":185},[],[187],{"type":49,"value":188},"init",{"type":49,"value":190}," \u002F ",{"type":43,"tag":58,"props":192,"children":194},{"className":193},[],[195],{"type":49,"value":196},"lint",{"type":49,"value":190},{"type":43,"tag":58,"props":199,"children":201},{"className":200},[],[202],{"type":49,"value":203},"validate",{"type":49,"value":190},{"type":43,"tag":58,"props":206,"children":208},{"className":207},[],[209],{"type":49,"value":210},"render",{"type":43,"tag":137,"props":212,"children":213},{},[214,216,221,223,229],{"type":49,"value":215},"pulled on demand via ",{"type":43,"tag":58,"props":217,"children":219},{"className":218},[],[220],{"type":49,"value":155},{"type":49,"value":222}," (needs npm-registry access at scaffold time; the project then pins a version in ",{"type":43,"tag":58,"props":224,"children":226},{"className":225},[],[227],{"type":49,"value":228},"dist\u002Fpackage.json",{"type":49,"value":157},{"type":43,"tag":110,"props":231,"children":232},{},[233,241,267],{"type":43,"tag":137,"props":234,"children":235},{},[236],{"type":43,"tag":79,"props":237,"children":238},{},[239],{"type":49,"value":240},"Headless Chromium + OS libs",{"type":43,"tag":137,"props":242,"children":243},{},[244,250,252,258,259,265],{"type":43,"tag":58,"props":245,"children":247},{"className":246},[],[248],{"type":49,"value":249},"npx hyperframes render",{"type":49,"value":251}," (puppeteer) + post-render QA gates (",{"type":43,"tag":58,"props":253,"children":255},{"className":254},[],[256],{"type":49,"value":257},"postcheck.py",{"type":49,"value":190},{"type":43,"tag":58,"props":260,"children":262},{"className":261},[],[263],{"type":49,"value":264},"precheck.py",{"type":49,"value":266}," drive headless Chrome)",{"type":43,"tag":137,"props":268,"children":269},{},[270,272,277,279,284,286,292,294,300],{"type":49,"value":271},"the browser itself is auto-downloaded by puppeteer on first ",{"type":43,"tag":58,"props":273,"children":275},{"className":274},[],[276],{"type":49,"value":155},{"type":49,"value":278},"; on ",{"type":43,"tag":79,"props":280,"children":281},{},[282],{"type":49,"value":283},"minimal Linux",{"type":49,"value":285}," you must also ",{"type":43,"tag":58,"props":287,"children":289},{"className":288},[],[290],{"type":49,"value":291},"apt install libnss3 libatk-bridge2.0-0 libgbm1 libasound2 libxkbcommon0 libgtk-3-0 fonts-noto-cjk",{"type":49,"value":293}," (else Chrome fails to launch, or CJK\u002Fformulas render as tofu boxes). Reuse a system Chrome via ",{"type":43,"tag":58,"props":295,"children":297},{"className":296},[],[298],{"type":49,"value":299},"export PUPPETEER_EXECUTABLE_PATH=\u002Fusr\u002Fbin\u002Fchromium",{"type":49,"value":301},".",{"type":43,"tag":110,"props":303,"children":304},{},[305,313,318],{"type":43,"tag":137,"props":306,"children":307},{},[308],{"type":43,"tag":79,"props":309,"children":310},{},[311],{"type":49,"value":312},"Python 3 + pip",{"type":43,"tag":137,"props":314,"children":315},{},[316],{"type":49,"value":317},"TTS script",{"type":43,"tag":137,"props":319,"children":320},{},[321],{"type":43,"tag":58,"props":322,"children":324},{"className":323},[],[325],{"type":49,"value":326},"python3 -m pip --version",{"type":43,"tag":110,"props":328,"children":329},{},[330,364,369],{"type":43,"tag":137,"props":331,"children":332},{},[333],{"type":43,"tag":79,"props":334,"children":335},{},[336,342,344,350,351,357,358],{"type":43,"tag":58,"props":337,"children":339},{"className":338},[],[340],{"type":49,"value":341},"dashscope",{"type":49,"value":343}," ",{"type":43,"tag":58,"props":345,"children":347},{"className":346},[],[348],{"type":49,"value":349},"soundfile",{"type":49,"value":343},{"type":43,"tag":58,"props":352,"children":354},{"className":353},[],[355],{"type":49,"value":356},"numpy",{"type":49,"value":343},{"type":43,"tag":58,"props":359,"children":361},{"className":360},[],[362],{"type":49,"value":363},"requests",{"type":43,"tag":137,"props":365,"children":366},{},[367],{"type":49,"value":368},"Step 3 TTS synthesis + assembly",{"type":43,"tag":137,"props":370,"children":371},{},[372],{"type":43,"tag":58,"props":373,"children":375},{"className":374},[],[376],{"type":49,"value":377},"python3 -m pip install dashscope soundfile numpy requests",{"type":43,"tag":110,"props":379,"children":380},{},[381,389,402],{"type":43,"tag":137,"props":382,"children":383},{},[384],{"type":43,"tag":79,"props":385,"children":386},{},[387],{"type":49,"value":388},"ffmpeg",{"type":43,"tag":137,"props":390,"children":391},{},[392,394,400],{"type":49,"value":393},"loudness normalization (",{"type":43,"tag":58,"props":395,"children":397},{"className":396},[],[398],{"type":49,"value":399},"loudnorm",{"type":49,"value":401},") + frame extraction for self-check",{"type":43,"tag":137,"props":403,"children":404},{},[405,411,412],{"type":43,"tag":58,"props":406,"children":408},{"className":407},[],[409],{"type":49,"value":410},"brew install ffmpeg",{"type":49,"value":190},{"type":43,"tag":58,"props":413,"children":415},{"className":414},[],[416],{"type":49,"value":417},"apt install ffmpeg",{"type":43,"tag":110,"props":419,"children":420},{},[421,433,445],{"type":43,"tag":137,"props":422,"children":423},{},[424],{"type":43,"tag":79,"props":425,"children":426},{},[427],{"type":43,"tag":58,"props":428,"children":430},{"className":429},[],[431],{"type":49,"value":432},"DASHSCOPE_API_KEY",{"type":43,"tag":137,"props":434,"children":435},{},[436,438,444],{"type":49,"value":437},"Qwen-TTS (",{"type":43,"tag":58,"props":439,"children":441},{"className":440},[],[442],{"type":49,"value":443},"qwen3-tts-flash",{"type":49,"value":157},{"type":43,"tag":137,"props":446,"children":447},{},[448,454,456,461,463,469,471,477,479,485,487],{"type":43,"tag":58,"props":449,"children":451},{"className":450},[],[452],{"type":49,"value":453},"export DASHSCOPE_API_KEY=\"sk-xxx\"",{"type":49,"value":455},", ",{"type":43,"tag":79,"props":457,"children":458},{},[459],{"type":49,"value":460},"or",{"type":49,"value":462}," put ",{"type":43,"tag":58,"props":464,"children":466},{"className":465},[],[467],{"type":49,"value":468},"DASHSCOPE_API_KEY=sk-xxx",{"type":49,"value":470}," in ",{"type":43,"tag":58,"props":472,"children":474},{"className":473},[],[475],{"type":49,"value":476},"~\u002F.qwen-mm-plugins\u002Fconfig",{"type":49,"value":478}," for GUI-launched setups that don't inherit shell exports. ",{"type":43,"tag":58,"props":480,"children":482},{"className":481},[],[483],{"type":49,"value":484},"$EDU_SKILL_ROOT\u002Fscripts\u002Fgenerate_voice.py",{"type":49,"value":486}," reads it at runtime — ",{"type":43,"tag":79,"props":488,"children":489},{},[490,492,498],{"type":49,"value":491},"never ",{"type":43,"tag":58,"props":493,"children":495},{"className":494},[],[496],{"type":49,"value":497},"cat",{"type":49,"value":499},"\u002Fpaste the key into the conversation.",{"type":43,"tag":501,"props":502,"children":503},"blockquote",{},[504],{"type":43,"tag":52,"props":505,"children":506},{},[507,512,513,519,521,526,528,534,536,542],{"type":43,"tag":79,"props":508,"children":509},{},[510],{"type":49,"value":511},"Network boundary:",{"type":49,"value":343},{"type":43,"tag":58,"props":514,"children":516},{"className":515},[],[517],{"type":49,"value":518},"npx hyperframes init",{"type":49,"value":520}," and the TTS calls need internet. The ",{"type":43,"tag":522,"props":523,"children":524},"em",{},[525],{"type":49,"value":210},{"type":49,"value":527}," itself is air-gapped — that is why fonts \u002F KaTeX \u002F GSAP must be self-hosted into ",{"type":43,"tag":58,"props":529,"children":531},{"className":530},[],[532],{"type":49,"value":533},"dist\u002F",{"type":49,"value":535}," (see Step 5 Prerequisites). DashScope TTS may rate-limit under high concurrency; if you hit ",{"type":43,"tag":58,"props":537,"children":539},{"className":538},[],[540],{"type":49,"value":541},"Throttling.RateQuota",{"type":49,"value":543},", lower the thread-pool worker count and add backoff (see step-3).",{"type":43,"tag":52,"props":545,"children":546},{},[547,549,555,557,568],{"type":49,"value":548},"Before Step 0, resolve ",{"type":43,"tag":58,"props":550,"children":552},{"className":551},[],[553],{"type":49,"value":554},"EDU_SKILL_ROOT",{"type":49,"value":556}," to the ",{"type":43,"tag":79,"props":558,"children":559},{},[560,562],{"type":49,"value":561},"absolute directory containing this ",{"type":43,"tag":58,"props":563,"children":565},{"className":564},[],[566],{"type":49,"value":567},"SKILL.md",{"type":49,"value":569},". All\nshipped scripts and assets must be addressed through that root; they are not in the user's project.\nShell tool calls do not necessarily share state, so set it in every command block that uses it (or\nsubstitute the resolved absolute path directly); never rely on an earlier shell invocation:",{"type":43,"tag":571,"props":572,"children":577},"pre",{"className":573,"code":574,"language":575,"meta":576,"style":576},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","EDU_SKILL_ROOT=\"\u003Cabsolute directory containing qwen-mm-plugins-edu-agent\u002FSKILL.md>\"\ntest -f \"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py\" || { echo \"invalid EDU_SKILL_ROOT\"; exit 1; }\n","bash","",[578],{"type":43,"tag":58,"props":579,"children":580},{"__ignoreMap":576},[581,614],{"type":43,"tag":582,"props":583,"children":586},"span",{"class":584,"line":585},"line",1,[587,592,598,603,609],{"type":43,"tag":582,"props":588,"children":590},{"style":589},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[591],{"type":49,"value":554},{"type":43,"tag":582,"props":593,"children":595},{"style":594},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[596],{"type":49,"value":597},"=",{"type":43,"tag":582,"props":599,"children":600},{"style":594},[601],{"type":49,"value":602},"\"",{"type":43,"tag":582,"props":604,"children":606},{"style":605},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[607],{"type":49,"value":608},"\u003Cabsolute directory containing qwen-mm-plugins-edu-agent\u002FSKILL.md>",{"type":43,"tag":582,"props":610,"children":611},{"style":594},[612],{"type":49,"value":613},"\"\n",{"type":43,"tag":582,"props":615,"children":617},{"class":584,"line":616},2,[618,624,629,634,639,644,648,653,658,663,667,672,676,681,686,692,696],{"type":43,"tag":582,"props":619,"children":621},{"style":620},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[622],{"type":49,"value":623},"test",{"type":43,"tag":582,"props":625,"children":626},{"style":605},[627],{"type":49,"value":628}," -f",{"type":43,"tag":582,"props":630,"children":631},{"style":594},[632],{"type":49,"value":633}," \"",{"type":43,"tag":582,"props":635,"children":636},{"style":589},[637],{"type":49,"value":638},"$EDU_SKILL_ROOT",{"type":43,"tag":582,"props":640,"children":641},{"style":605},[642],{"type":49,"value":643},"\u002Fscripts\u002Fprecheck.py",{"type":43,"tag":582,"props":645,"children":646},{"style":594},[647],{"type":49,"value":602},{"type":43,"tag":582,"props":649,"children":650},{"style":594},[651],{"type":49,"value":652}," ||",{"type":43,"tag":582,"props":654,"children":655},{"style":594},[656],{"type":49,"value":657}," {",{"type":43,"tag":582,"props":659,"children":660},{"style":620},[661],{"type":49,"value":662}," echo",{"type":43,"tag":582,"props":664,"children":665},{"style":594},[666],{"type":49,"value":633},{"type":43,"tag":582,"props":668,"children":669},{"style":605},[670],{"type":49,"value":671},"invalid EDU_SKILL_ROOT",{"type":43,"tag":582,"props":673,"children":674},{"style":594},[675],{"type":49,"value":602},{"type":43,"tag":582,"props":677,"children":678},{"style":594},[679],{"type":49,"value":680},";",{"type":43,"tag":582,"props":682,"children":683},{"style":620},[684],{"type":49,"value":685}," exit",{"type":43,"tag":582,"props":687,"children":689},{"style":688},"--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C",[690],{"type":49,"value":691}," 1",{"type":43,"tag":582,"props":693,"children":694},{"style":594},[695],{"type":49,"value":680},{"type":43,"tag":582,"props":697,"children":698},{"style":594},[699],{"type":49,"value":700}," }\n",{"type":43,"tag":67,"props":702,"children":704},{"id":703},"pipeline-overview",[705],{"type":49,"value":706},"Pipeline Overview",{"type":43,"tag":102,"props":708,"children":709},{},[710,736],{"type":43,"tag":106,"props":711,"children":712},{},[713],{"type":43,"tag":110,"props":714,"children":715},{},[716,721,726,731],{"type":43,"tag":114,"props":717,"children":718},{},[719],{"type":49,"value":720},"Step",{"type":43,"tag":114,"props":722,"children":723},{},[724],{"type":49,"value":725},"Name",{"type":43,"tag":114,"props":727,"children":728},{},[729],{"type":49,"value":730},"Artifact",{"type":43,"tag":114,"props":732,"children":733},{},[734],{"type":49,"value":735},"Reference",{"type":43,"tag":130,"props":737,"children":738},{},[739,771,802,833,881,912,943],{"type":43,"tag":110,"props":740,"children":741},{},[742,747,752,761],{"type":43,"tag":137,"props":743,"children":744},{},[745],{"type":49,"value":746},"0",{"type":43,"tag":137,"props":748,"children":749},{},[750],{"type":49,"value":751},"Image Input & Problem Extraction",{"type":43,"tag":137,"props":753,"children":754},{},[755],{"type":43,"tag":58,"props":756,"children":758},{"className":757},[],[759],{"type":49,"value":760},"PROBLEM.md",{"type":43,"tag":137,"props":762,"children":763},{},[764],{"type":43,"tag":765,"props":766,"children":768},"a",{"href":767},"references\u002Fstep-0-image-input.md",[769],{"type":49,"value":770},"step-0-image-input.md",{"type":43,"tag":110,"props":772,"children":773},{},[774,779,784,793],{"type":43,"tag":137,"props":775,"children":776},{},[777],{"type":49,"value":778},"1",{"type":43,"tag":137,"props":780,"children":781},{},[782],{"type":49,"value":783},"Problem Analysis",{"type":43,"tag":137,"props":785,"children":786},{},[787],{"type":43,"tag":58,"props":788,"children":790},{"className":789},[],[791],{"type":49,"value":792},"ANALYSIS.md",{"type":43,"tag":137,"props":794,"children":795},{},[796],{"type":43,"tag":765,"props":797,"children":799},{"href":798},"references\u002Fstep-1-problem-analysis.md",[800],{"type":49,"value":801},"step-1-problem-analysis.md",{"type":43,"tag":110,"props":803,"children":804},{},[805,810,815,824],{"type":43,"tag":137,"props":806,"children":807},{},[808],{"type":49,"value":809},"2",{"type":43,"tag":137,"props":811,"children":812},{},[813],{"type":49,"value":814},"Teaching Script",{"type":43,"tag":137,"props":816,"children":817},{},[818],{"type":43,"tag":58,"props":819,"children":821},{"className":820},[],[822],{"type":49,"value":823},"SCRIPT.md",{"type":43,"tag":137,"props":825,"children":826},{},[827],{"type":43,"tag":765,"props":828,"children":830},{"href":829},"references\u002Fstep-2-teaching-script.md",[831],{"type":49,"value":832},"step-2-teaching-script.md",{"type":43,"tag":110,"props":834,"children":835},{},[836,841,846,872],{"type":43,"tag":137,"props":837,"children":838},{},[839],{"type":49,"value":840},"3",{"type":43,"tag":137,"props":842,"children":843},{},[844],{"type":49,"value":845},"Voice Generation",{"type":43,"tag":137,"props":847,"children":848},{},[849,855,857,863,864,870],{"type":43,"tag":58,"props":850,"children":852},{"className":851},[],[853],{"type":49,"value":854},"narration.wav",{"type":49,"value":856}," + ",{"type":43,"tag":58,"props":858,"children":860},{"className":859},[],[861],{"type":49,"value":862},"transcript.json",{"type":49,"value":856},{"type":43,"tag":58,"props":865,"children":867},{"className":866},[],[868],{"type":49,"value":869},"captions.json",{"type":49,"value":871}," (sentence-by-sentence TTS, no Whisper)",{"type":43,"tag":137,"props":873,"children":874},{},[875],{"type":43,"tag":765,"props":876,"children":878},{"href":877},"references\u002Fstep-3-voice-generation.md",[879],{"type":49,"value":880},"step-3-voice-generation.md",{"type":43,"tag":110,"props":882,"children":883},{},[884,889,894,903],{"type":43,"tag":137,"props":885,"children":886},{},[887],{"type":49,"value":888},"4",{"type":43,"tag":137,"props":890,"children":891},{},[892],{"type":49,"value":893},"Storyboard",{"type":43,"tag":137,"props":895,"children":896},{},[897],{"type":43,"tag":58,"props":898,"children":900},{"className":899},[],[901],{"type":49,"value":902},"STORYBOARD.md",{"type":43,"tag":137,"props":904,"children":905},{},[906],{"type":43,"tag":765,"props":907,"children":909},{"href":908},"references\u002Fstep-4-storyboard.md",[910],{"type":49,"value":911},"step-4-storyboard.md",{"type":43,"tag":110,"props":913,"children":914},{},[915,920,925,934],{"type":43,"tag":137,"props":916,"children":917},{},[918],{"type":49,"value":919},"5",{"type":43,"tag":137,"props":921,"children":922},{},[923],{"type":49,"value":924},"Build Components",{"type":43,"tag":137,"props":926,"children":927},{},[928],{"type":43,"tag":58,"props":929,"children":931},{"className":930},[],[932],{"type":49,"value":933},"compositions\u002F*.html",{"type":43,"tag":137,"props":935,"children":936},{},[937],{"type":43,"tag":765,"props":938,"children":940},{"href":939},"references\u002Fstep-5-build-components.md",[941],{"type":49,"value":942},"step-5-build-components.md",{"type":43,"tag":110,"props":944,"children":945},{},[946,951,956,967],{"type":43,"tag":137,"props":947,"children":948},{},[949],{"type":49,"value":950},"6",{"type":43,"tag":137,"props":952,"children":953},{},[954],{"type":49,"value":955},"Compose & Render",{"type":43,"tag":137,"props":957,"children":958},{},[959,965],{"type":43,"tag":58,"props":960,"children":962},{"className":961},[],[963],{"type":49,"value":964},"index.html",{"type":49,"value":966}," + MP4",{"type":43,"tag":137,"props":968,"children":969},{},[970],{"type":43,"tag":765,"props":971,"children":973},{"href":972},"references\u002Fstep-6-compose-render.md",[974],{"type":49,"value":975},"step-6-compose-render.md",{"type":43,"tag":67,"props":977,"children":979},{"id":978},"step-0-image-input-problem-extraction",[980],{"type":49,"value":981},"Step 0: Image Input & Problem Extraction",{"type":43,"tag":52,"props":983,"children":984},{},[985,987,991],{"type":49,"value":986},"Read ",{"type":43,"tag":765,"props":988,"children":989},{"href":767},[990],{"type":49,"value":767},{"type":49,"value":301},{"type":43,"tag":52,"props":993,"children":994},{},[995,997,1003,1005,1011],{"type":49,"value":996},"Read all images from the ",{"type":43,"tag":58,"props":998,"children":1000},{"className":999},[],[1001],{"type":49,"value":1002},"image_assets\u002F",{"type":49,"value":1004}," folder using the ",{"type":43,"tag":58,"props":1006,"children":1008},{"className":1007},[],[1009],{"type":49,"value":1010},"Read",{"type":49,"value":1012}," tool (Claude's multimodal vision directly interprets image content). Extract the complete problem text, convert all math expressions to LaTeX, and describe any figures or diagrams. If JSONL metadata is available (subject, sub_subject, question_type, stepwise_explanation), use it as context hints but treat the image as ground truth.",{"type":43,"tag":1014,"props":1015,"children":1016},"hard-gate",{},[1017],{"type":49,"value":1018},"\n`PROBLEM.md` must exist with: complete problem text in Chinese, all math expressions in LaTeX, and figure descriptions (if applicable). All images in `image_assets\u002F` must have been read.\n",{"type":43,"tag":67,"props":1020,"children":1022},{"id":1021},"step-1-problem-analysis",[1023],{"type":49,"value":1024},"Step 1: Problem Analysis",{"type":43,"tag":52,"props":1026,"children":1027},{},[1028,1029,1033],{"type":49,"value":986},{"type":43,"tag":765,"props":1030,"children":1031},{"href":798},[1032],{"type":49,"value":798},{"type":49,"value":301},{"type":43,"tag":52,"props":1035,"children":1036},{},[1037,1039,1044],{"type":49,"value":1038},"Parse the input math problem, classify its type, extract knowledge points, and produce a complete solution outline with numbered steps. When ",{"type":43,"tag":58,"props":1040,"children":1042},{"className":1041},[],[1043],{"type":49,"value":760},{"type":49,"value":1045}," exists from Step 0, use it as the primary input source.",{"type":43,"tag":1014,"props":1047,"children":1048},{},[1049],{"type":49,"value":1050},"\n`ANALYSIS.md` must exist with: problem statement, type classification, solution steps, and final answer — all verified for correctness.\n",{"type":43,"tag":67,"props":1052,"children":1054},{"id":1053},"step-2-teaching-script",[1055],{"type":49,"value":1056},"Step 2: Teaching Script",{"type":43,"tag":52,"props":1058,"children":1059},{},[1060,1061,1065],{"type":49,"value":986},{"type":43,"tag":765,"props":1062,"children":1063},{"href":829},[1064],{"type":49,"value":829},{"type":49,"value":301},{"type":43,"tag":52,"props":1067,"children":1068},{},[1069],{"type":49,"value":1070},"Write the Chinese narration script with scene markers. Apply math symbol pronunciation rules. Target pacing: 3.5-4.0 Chinese characters per second.",{"type":43,"tag":1014,"props":1072,"children":1073},{},[1074],{"type":49,"value":1075},"\n`SCRIPT.md` must exist with scene-separated narration text in Chinese. All math symbols converted to spoken Chinese.\n",{"type":43,"tag":67,"props":1077,"children":1079},{"id":1078},"step-3-voice-generation",[1080],{"type":49,"value":1081},"Step 3: Voice Generation",{"type":43,"tag":52,"props":1083,"children":1084},{},[1085,1086,1090,1092,1097,1099,1105,1107,1112,1114,1119,1121,1126],{"type":49,"value":986},{"type":43,"tag":765,"props":1087,"children":1088},{"href":877},[1089],{"type":49,"value":877},{"type":49,"value":1091},". TTS strategy: ",{"type":43,"tag":79,"props":1093,"children":1094},{},[1095],{"type":49,"value":1096},"DashScope Qwen-TTS via the official SDK",{"type":49,"value":1098}," (",{"type":43,"tag":58,"props":1100,"children":1102},{"className":1101},[],[1103],{"type":49,"value":1104},"dashscope.MultiModalConversation",{"type":49,"value":1106},", model ",{"type":43,"tag":58,"props":1108,"children":1110},{"className":1109},[],[1111],{"type":49,"value":443},{"type":49,"value":1113},", HTTP — returns a WAV URL) — no self-hosted TTS node, no URL wiring. ",{"type":43,"tag":79,"props":1115,"children":1116},{},[1117],{"type":49,"value":1118},"Speed + accuracy:",{"type":49,"value":1120}," synthesize every sentence ",{"type":43,"tag":79,"props":1122,"children":1123},{},[1124],{"type":49,"value":1125},"concurrently",{"type":49,"value":1127}," through a thread pool (wall-clock ≈ the slowest single sentence, not the sum), then measure each returned clip's real duration for exact per-sentence timestamps. No Whisper dependency.",{"type":43,"tag":52,"props":1129,"children":1130},{},[1131,1133,1138,1140,1145],{"type":49,"value":1132},"Generate standard Mandarin TTS audio. Each sentence's exact duration is measured from its returned audio clip, producing both ",{"type":43,"tag":58,"props":1134,"children":1136},{"className":1135},[],[1137],{"type":49,"value":854},{"type":49,"value":1139}," and ",{"type":43,"tag":58,"props":1141,"children":1143},{"className":1142},[],[1144],{"type":49,"value":862},{"type":49,"value":1146}," in one pass with 100% accurate timestamps. No Whisper transcription needed.",{"type":43,"tag":1014,"props":1148,"children":1149},{},[1150],{"type":49,"value":1151},"\n`narration.wav`, `transcript.json`, and `captions.json` must exist. Audio must be loudness-normalized with `ffmpeg loudnorm` (EBU R128, -16 LUFS). Audio is generated with DashScope Qwen-TTS (`dashscope.MultiModalConversation`, model `qwen3-tts-flash`) — `DASHSCOPE_API_KEY` required. Timestamps are measured from TTS output (not estimated). Text in transcript\u002Fcaptions comes from the original script. Timestamps mapped to scene boundaries.\n",{"type":43,"tag":67,"props":1153,"children":1155},{"id":1154},"step-4-storyboard",[1156],{"type":49,"value":1157},"Step 4: Storyboard",{"type":43,"tag":52,"props":1159,"children":1160},{},[1161,1162,1166,1168,1173],{"type":49,"value":986},{"type":43,"tag":765,"props":1163,"children":1164},{"href":908},[1165],{"type":49,"value":908},{"type":49,"value":1167},". Read ",{"type":43,"tag":765,"props":1169,"children":1171},{"href":1170},"design-system.md",[1172],{"type":49,"value":1170},{"type":49,"value":1174}," for visual tokens.",{"type":43,"tag":52,"props":1176,"children":1177},{},[1178,1180,1186,1188,1193,1195,1200,1202,1208],{"type":49,"value":1179},"Design per-scene visual layout, assign component templates from ",{"type":43,"tag":765,"props":1181,"children":1183},{"href":1182},"references\u002Fmath-components.md",[1184],{"type":49,"value":1185},"math-components.md",{"type":49,"value":1187},", and plan transitions. Check ",{"type":43,"tag":765,"props":1189,"children":1191},{"href":1190},"assets\u002FASSET_CATALOG.md",[1192],{"type":49,"value":1190},{"type":49,"value":1194}," to identify which pre-built visual components can be reused in each scene. ",{"type":43,"tag":79,"props":1196,"children":1197},{},[1198],{"type":49,"value":1199},"Select a background theme",{"type":49,"value":1201}," from the Background Theme Catalog in design-system.md — set it in the Global Direction block (default: ",{"type":43,"tag":58,"props":1203,"children":1205},{"className":1204},[],[1206],{"type":49,"value":1207},"aurora-scholar",{"type":49,"value":1209},").",{"type":43,"tag":1014,"props":1211,"children":1212},{},[1213],{"type":49,"value":1214},"\n`STORYBOARD.md` must exist with per-scene direction: component type, layout, animation choreography, and transition choice.\n",{"type":43,"tag":67,"props":1216,"children":1218},{"id":1217},"step-5-build-components",[1219],{"type":49,"value":1220},"Step 5: Build Components",{"type":43,"tag":52,"props":1222,"children":1223},{},[1224,1226,1232,1234,1240,1242,1246,1247,1251,1253,1257,1259,1264,1266,1271,1273,1278,1280,1285,1286,1291,1293,1303,1305,1310,1312,1322,1324,1330,1332,1338],{"type":49,"value":1225},"Read the ",{"type":43,"tag":58,"props":1227,"children":1229},{"className":1228},[],[1230],{"type":49,"value":1231},"hyperframes",{"type":49,"value":1233}," skill — every composition authoring rule applies. Read the ",{"type":43,"tag":58,"props":1235,"children":1237},{"className":1236},[],[1238],{"type":49,"value":1239},"gsap",{"type":49,"value":1241}," skill for animation patterns. Read ",{"type":43,"tag":765,"props":1243,"children":1244},{"href":939},[1245],{"type":49,"value":939},{"type":49,"value":1167},{"type":43,"tag":765,"props":1248,"children":1249},{"href":1182},[1250],{"type":49,"value":1182},{"type":49,"value":1252}," for component templates. Read ",{"type":43,"tag":765,"props":1254,"children":1255},{"href":1190},[1256],{"type":49,"value":1190},{"type":49,"value":1258}," for pre-built visual components. ",{"type":43,"tag":79,"props":1260,"children":1261},{},[1262],{"type":49,"value":1263},"For geometry diagrams (几何图形)",{"type":49,"value":1265},", read ",{"type":43,"tag":765,"props":1267,"children":1269},{"href":1268},"references\u002Fgeometry-construction-guide.md",[1270],{"type":49,"value":1268},{"type":49,"value":1272}," for coordinate computation patterns, angle arc construction, and complete worked examples (triangles, quadrilaterals, circles, rotations, reflections); also read ",{"type":43,"tag":765,"props":1274,"children":1276},{"href":1275},"references\u002Fgolden-example-geometry.md",[1277],{"type":49,"value":1275},{"type":49,"value":1279}," for a complete golden pipeline (PROBLEM → HTML compositions) showing the proven split layout, draw-on animation choreography, and narration-synced note blocks from a top-quality geometry proof video. ",{"type":43,"tag":79,"props":1281,"children":1282},{},[1283],{"type":49,"value":1284},"For circuit schematic diagrams (电路图)",{"type":49,"value":1265},{"type":43,"tag":765,"props":1287,"children":1289},{"href":1288},"references\u002Fcircuit-schematic-guide.md",[1290],{"type":49,"value":1288},{"type":49,"value":1292}," for physics rules and SVG symbol templates. ",{"type":43,"tag":79,"props":1294,"children":1295},{},[1296,1298],{"type":49,"value":1297},"For any scene that must ANIMATE A PROCESS with real motion (生物过程\u002F受力矢量\u002F波动\u002F滴定\u002F运动演示 — things that split, move, get pulled, or a quantity that changes across stages), read ",{"type":43,"tag":765,"props":1299,"children":1301},{"href":1300},"references\u002Fexample-process-animation.md",[1302],{"type":49,"value":1300},{"type":49,"value":1304}," — a top-scoring few-shot (洋葱有丝分裂) with two complete reference scenes and copy-me techniques: staged phase reveal, split-and-move, ",{"type":43,"tag":79,"props":1306,"children":1307},{},[1308],{"type":49,"value":1309},"connectors that track a moving object and shorten (纺锤丝\u002F绳\u002F矢量牵引)",{"type":49,"value":1311},", live count-up, and a stepped quantity-vs-stage chart (plus the no-360°-spin rule). ",{"type":43,"tag":79,"props":1313,"children":1314},{},[1315,1317],{"type":49,"value":1316},"🧬 MANDATORY for ANY 染色体\u002F细胞分裂题目 (有丝分裂\u002F减数分裂\u002F染色单体\u002F着丝点\u002F纺锤丝\u002F移向两极): you MUST open and copy ",{"type":43,"tag":765,"props":1318,"children":1320},{"href":1319},"references\u002Fexamples\u002Fmitosis-anaphase.scene.html",[1321],{"type":49,"value":1319},{"type":49,"value":1323}," — the spindle fibers must be animated ",{"type":43,"tag":58,"props":1325,"children":1327},{"className":1326},[],[1328],{"type":49,"value":1329},"\u003Cline>",{"type":49,"value":1331},"s that TRACK the moving chromatids and SHORTEN (pull them to the poles), with a live 染色体数 count-up, and NO 360° spin on chromosomes. This is enforced at render time by ",{"type":43,"tag":58,"props":1333,"children":1335},{"className":1334},[],[1336],{"type":49,"value":1337},"scripts\u002Fcheck_chromosome_example.py",{"type":49,"value":1339}," (a chromosome cell-division scene without the fiber-shorten tween, or with a 360° spin, FAILS precheck).",{"type":43,"tag":52,"props":1341,"children":1342},{},[1343,1345,1351,1353,1359,1361,1367,1369,1396,1397,1409,1410,1415,1417,1422,1424,1430,1431,1437,1438,1444,1445,1451,1452,1458,1459,1465],{"type":49,"value":1344},"Scaffold the project with ",{"type":43,"tag":58,"props":1346,"children":1348},{"className":1347},[],[1349],{"type":49,"value":1350},"npx hyperframes init dist --non-interactive --example blank",{"type":49,"value":1352}," (the ",{"type":43,"tag":58,"props":1354,"children":1356},{"className":1355},[],[1357],{"type":49,"value":1358},"--example blank",{"type":49,"value":1360}," flag is required — hyperframes ≥0.7.77 rejects a bare ",{"type":43,"tag":58,"props":1362,"children":1364},{"className":1363},[],[1365],{"type":49,"value":1366},"--non-interactive",{"type":49,"value":1368},"). ",{"type":43,"tag":79,"props":1370,"children":1371},{},[1372,1374,1379,1381,1387,1389,1395],{"type":49,"value":1373},"The project root is the current workspace and the scaffold dir MUST be exactly ",{"type":43,"tag":58,"props":1375,"children":1377},{"className":1376},[],[1378],{"type":49,"value":533},{"type":49,"value":1380}," at that root — never nest it inside another folder (e.g. NOT ",{"type":43,"tag":58,"props":1382,"children":1384},{"className":1383},[],[1385],{"type":49,"value":1386},"math-tutorial-output\u002Fdist",{"type":49,"value":1388},"); the renderer and all gates assume ",{"type":43,"tag":58,"props":1390,"children":1392},{"className":1391},[],[1393],{"type":49,"value":1394},".\u002Fdist",{"type":49,"value":301},{"type":49,"value":343},{"type":43,"tag":79,"props":1398,"children":1399},{},[1400,1402,1407],{"type":49,"value":1401},"Build ONE composition per scene listed in ",{"type":43,"tag":58,"props":1403,"children":1405},{"className":1404},[],[1406],{"type":49,"value":902},{"type":49,"value":1408}," — one special ray \u002F one solution step \u002F one concept per scene; NEVER cram several planned scenes into a single composition (that is the #1 cause of under-rendered, blank, or half-empty scenes and of run-to-run instability).",{"type":49,"value":343},{"type":43,"tag":79,"props":1411,"children":1412},{},[1413],{"type":49,"value":1414},"Before writing custom HTML for any visual object",{"type":49,"value":1416},", check the ",{"type":43,"tag":765,"props":1418,"children":1420},{"href":1419},"assets\u002F",[1421],{"type":49,"value":1419},{"type":49,"value":1423}," directory — it contains 83 pre-built K12 components (motion, optics, circuit, mechanics, fluid, chemistry, wave, indicators, math), including 6 circuit schematic symbols (",{"type":43,"tag":58,"props":1425,"children":1427},{"className":1426},[],[1428],{"type":49,"value":1429},"sch-battery",{"type":49,"value":455},{"type":43,"tag":58,"props":1432,"children":1434},{"className":1433},[],[1435],{"type":49,"value":1436},"sch-ammeter",{"type":49,"value":455},{"type":43,"tag":58,"props":1439,"children":1441},{"className":1440},[],[1442],{"type":49,"value":1443},"sch-voltmeter",{"type":49,"value":455},{"type":43,"tag":58,"props":1446,"children":1448},{"className":1447},[],[1449],{"type":49,"value":1450},"sch-switch",{"type":49,"value":455},{"type":43,"tag":58,"props":1453,"children":1455},{"className":1454},[],[1456],{"type":49,"value":1457},"sch-bulb",{"type":49,"value":455},{"type":43,"tag":58,"props":1460,"children":1462},{"className":1461},[],[1463],{"type":49,"value":1464},"sch-resistor",{"type":49,"value":1466},"). If a matching component exists, copy its CSS + HTML + JS hooks verbatim into the composition instead of building from scratch.",{"type":43,"tag":1014,"props":1468,"children":1469},{},[1470,1472,1532,1560,1599,1607,1611,1624,2356,2361,2424,2430,2448,2472,2538,2643,2749],{"type":49,"value":1471},"\n**>>> MANDATORY: run precheck and loop until it passes \u003C\u003C\u003C**\n",{"type":43,"tag":571,"props":1473,"children":1475},{"className":573,"code":1474,"language":575,"meta":576,"style":576},"EDU_SKILL_ROOT=\"\u003Cabsolute directory containing qwen-mm-plugins-edu-agent\u002FSKILL.md>\"\npython3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py\" dist\n",[1476],{"type":43,"tag":58,"props":1477,"children":1478},{"__ignoreMap":576},[1479,1502],{"type":43,"tag":582,"props":1480,"children":1481},{"class":584,"line":585},[1482,1486,1490,1494,1498],{"type":43,"tag":582,"props":1483,"children":1484},{"style":589},[1485],{"type":49,"value":554},{"type":43,"tag":582,"props":1487,"children":1488},{"style":594},[1489],{"type":49,"value":597},{"type":43,"tag":582,"props":1491,"children":1492},{"style":594},[1493],{"type":49,"value":602},{"type":43,"tag":582,"props":1495,"children":1496},{"style":605},[1497],{"type":49,"value":608},{"type":43,"tag":582,"props":1499,"children":1500},{"style":594},[1501],{"type":49,"value":613},{"type":43,"tag":582,"props":1503,"children":1504},{"class":584,"line":616},[1505,1511,1515,1519,1523,1527],{"type":43,"tag":582,"props":1506,"children":1508},{"style":1507},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[1509],{"type":49,"value":1510},"python3",{"type":43,"tag":582,"props":1512,"children":1513},{"style":594},[1514],{"type":49,"value":633},{"type":43,"tag":582,"props":1516,"children":1517},{"style":589},[1518],{"type":49,"value":638},{"type":43,"tag":582,"props":1520,"children":1521},{"style":605},[1522],{"type":49,"value":643},{"type":43,"tag":582,"props":1524,"children":1525},{"style":594},[1526],{"type":49,"value":602},{"type":43,"tag":582,"props":1528,"children":1529},{"style":605},[1530],{"type":49,"value":1531}," dist\n",{"type":43,"tag":52,"props":1533,"children":1534},{},[1535,1537,1550,1552,1558],{"type":49,"value":1536},"This script auto-fixes LaTeX escaping, then runs all validation checks. ",{"type":43,"tag":79,"props":1538,"children":1539},{},[1540,1542,1548],{"type":49,"value":1541},"You MUST see ",{"type":43,"tag":58,"props":1543,"children":1545},{"className":1544},[],[1546],{"type":49,"value":1547},"ALL CHECKS PASSED",{"type":49,"value":1549}," in the output before proceeding to Step 6.",{"type":49,"value":1551}," If any check shows ",{"type":43,"tag":58,"props":1553,"children":1555},{"className":1554},[],[1556],{"type":49,"value":1557},"FAIL",{"type":49,"value":1559},":",{"type":43,"tag":1561,"props":1562,"children":1563},"ol",{},[1564,1570,1575,1586],{"type":43,"tag":1565,"props":1566,"children":1567},"li",{},[1568],{"type":49,"value":1569},"Read the FAIL message — it prints the exact file, line, and fix instruction",{"type":43,"tag":1565,"props":1571,"children":1572},{},[1573],{"type":49,"value":1574},"Apply the fix to the offending file(s)",{"type":43,"tag":1565,"props":1576,"children":1577},{},[1578,1580],{"type":49,"value":1579},"Re-run ",{"type":43,"tag":58,"props":1581,"children":1583},{"className":1582},[],[1584],{"type":49,"value":1585},"python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py\" dist",{"type":43,"tag":1565,"props":1587,"children":1588},{},[1589],{"type":43,"tag":79,"props":1590,"children":1591},{},[1592,1594],{"type":49,"value":1593},"Repeat steps 1-3 until the output contains ",{"type":43,"tag":58,"props":1595,"children":1597},{"className":1596},[],[1598],{"type":49,"value":1547},{"type":43,"tag":52,"props":1600,"children":1601},{},[1602],{"type":43,"tag":79,"props":1603,"children":1604},{},[1605],{"type":49,"value":1606},"Do NOT proceed to Step 6 with failing checks. Do NOT skip precheck. Do NOT treat precheck failures as warnings.",{"type":43,"tag":1608,"props":1609,"children":1610},"hr",{},[],{"type":43,"tag":52,"props":1612,"children":1613},{},[1614,1616,1622],{"type":49,"value":1615},"The precheck validates all of the following (each is also a standalone script in ",{"type":43,"tag":58,"props":1617,"children":1619},{"className":1618},[],[1620],{"type":49,"value":1621},"scripts\u002F",{"type":49,"value":1623},"):",{"type":43,"tag":1625,"props":1626,"children":1627},"ul",{},[1628,1654,1672,1706,1732,1821,1855,1865,1875,1914,1924,1964,1994,2026,2066,2147,2192,2216,2248,2270,2293,2330],{"type":43,"tag":1565,"props":1629,"children":1630},{},[1631,1636,1638,1644,1646,1652],{"type":43,"tag":79,"props":1632,"children":1633},{},[1634],{"type":49,"value":1635},"Asset mirror",{"type":49,"value":1637}," — GSAP\u002FKaTeX\u002Ffonts mirrored into ",{"type":43,"tag":58,"props":1639,"children":1641},{"className":1640},[],[1642],{"type":49,"value":1643},"compositions\u002F",{"type":49,"value":1645}," (sub-compositions resolve ",{"type":43,"tag":58,"props":1647,"children":1649},{"className":1648},[],[1650],{"type":49,"value":1651},".\u002F",{"type":49,"value":1653}," relative to their own directory; without the mirror, GSAP\u002FKaTeX\u002Ffonts silently fail → blank scenes)",{"type":43,"tag":1565,"props":1655,"children":1656},{},[1657,1662,1664,1670],{"type":43,"tag":79,"props":1658,"children":1659},{},[1660],{"type":49,"value":1661},"Composition format",{"type":49,"value":1663}," — sub-compositions are HTML fragments, not full ",{"type":43,"tag":58,"props":1665,"children":1667},{"className":1666},[],[1668],{"type":49,"value":1669},"\u003C!doctype>",{"type":49,"value":1671}," documents (full docs → blank panels)",{"type":43,"tag":1565,"props":1673,"children":1674},{},[1675,1680,1682,1688,1690,1696,1698,1704],{"type":43,"tag":79,"props":1676,"children":1677},{},[1678],{"type":49,"value":1679},"No CDN URLs",{"type":49,"value":1681}," — no ",{"type":43,"tag":58,"props":1683,"children":1685},{"className":1684},[],[1686],{"type":49,"value":1687},"cdn.jsdelivr.net",{"type":49,"value":1689}," or external ",{"type":43,"tag":58,"props":1691,"children":1693},{"className":1692},[],[1694],{"type":49,"value":1695},"src",{"type":49,"value":1697},"\u002F",{"type":43,"tag":58,"props":1699,"children":1701},{"className":1700},[],[1702],{"type":49,"value":1703},"href",{"type":49,"value":1705}," (render sandbox is air-gapped → all external loads fail silently)",{"type":43,"tag":1565,"props":1707,"children":1708},{},[1709,1714,1716,1722,1724,1730],{"type":43,"tag":79,"props":1710,"children":1711},{},[1712],{"type":49,"value":1713},"KaTeX CJK",{"type":49,"value":1715}," — no Chinese inside ",{"type":43,"tag":58,"props":1717,"children":1719},{"className":1718},[],[1720],{"type":49,"value":1721},"katex.render()",{"type":49,"value":1723}," or ",{"type":43,"tag":58,"props":1725,"children":1727},{"className":1726},[],[1728],{"type":49,"value":1729},"data-tex",{"type":49,"value":1731}," (KaTeX fonts lack CJK → tofu)",{"type":43,"tag":1565,"props":1733,"children":1734},{},[1735,1740,1742,1748,1749,1755,1757,1763,1765,1771,1773,1779,1780,1786,1788,1793,1795,1800,1802,1807,1808,1813,1815],{"type":43,"tag":79,"props":1736,"children":1737},{},[1738],{"type":49,"value":1739},"CJK font on Chinese text (中文必须用中文字体)",{"type":49,"value":1741}," — any SVG ",{"type":43,"tag":58,"props":1743,"children":1745},{"className":1744},[],[1746],{"type":49,"value":1747},"\u003Ctext>",{"type":49,"value":1697},{"type":43,"tag":58,"props":1750,"children":1752},{"className":1751},[],[1753],{"type":49,"value":1754},"\u003Ctspan>",{"type":49,"value":1756}," or inline-styled element whose text contains Chinese MUST use a stack including ",{"type":43,"tag":58,"props":1758,"children":1760},{"className":1759},[],[1761],{"type":49,"value":1762},"Noto Sans SC",{"type":49,"value":1764}," (e.g. ",{"type":43,"tag":58,"props":1766,"children":1768},{"className":1767},[],[1769],{"type":49,"value":1770},"font-family=\"Noto Sans SC, Inter, sans-serif\"",{"type":49,"value":1772},"); ",{"type":43,"tag":58,"props":1774,"children":1776},{"className":1775},[],[1777],{"type":49,"value":1778},"Inter",{"type":49,"value":1697},{"type":43,"tag":58,"props":1781,"children":1783},{"className":1782},[],[1784],{"type":49,"value":1785},"sans-serif",{"type":49,"value":1787}," alone has no CJK glyphs so Chinese renders as ",{"type":43,"tag":79,"props":1789,"children":1790},{},[1791],{"type":49,"value":1792},"NO GLYPH \u002F 豆腐块",{"type":49,"value":1794},". Don't mix scripts in one ",{"type":43,"tag":58,"props":1796,"children":1798},{"className":1797},[],[1799],{"type":49,"value":1747},{"type":49,"value":1801}," — split Latin (Inter) and Chinese (Noto Sans SC) into separate ",{"type":43,"tag":58,"props":1803,"children":1805},{"className":1804},[],[1806],{"type":49,"value":1754},{"type":49,"value":1697},{"type":43,"tag":58,"props":1809,"children":1811},{"className":1810},[],[1812],{"type":49,"value":1747},{"type":49,"value":1814},". Gate: ",{"type":43,"tag":58,"props":1816,"children":1818},{"className":1817},[],[1819],{"type":49,"value":1820},"scripts\u002Fcheck_cjk_font.py",{"type":43,"tag":1565,"props":1822,"children":1823},{},[1824,1829,1831,1837,1839,1845,1847,1853],{"type":43,"tag":79,"props":1825,"children":1826},{},[1827],{"type":49,"value":1828},"KaTeX escaping",{"type":49,"value":1830}," — LaTeX in JS strings double-escaped (",{"type":43,"tag":58,"props":1832,"children":1834},{"className":1833},[],[1835],{"type":49,"value":1836},"\\\\\\\\dfrac",{"type":49,"value":1838},", not ",{"type":43,"tag":58,"props":1840,"children":1842},{"className":1841},[],[1843],{"type":49,"value":1844},"\\\\dfrac",{"type":49,"value":1846},"; JS eats single ",{"type":43,"tag":58,"props":1848,"children":1850},{"className":1849},[],[1851],{"type":49,"value":1852},"\\\\d",{"type":49,"value":1854}," → \"dfrac12\" instead of fraction)",{"type":43,"tag":1565,"props":1856,"children":1857},{},[1858,1863],{"type":43,"tag":79,"props":1859,"children":1860},{},[1861],{"type":49,"value":1862},"Unrendered fractions",{"type":49,"value":1864}," — no literal \"dfrac\"\u002F\"frac{\" leaked into visible HTML text",{"type":43,"tag":1565,"props":1866,"children":1867},{},[1868,1873],{"type":43,"tag":79,"props":1869,"children":1870},{},[1871],{"type":49,"value":1872},"Caption size",{"type":49,"value":1874}," — font-size 36–40px (not 64px giant subtitles)",{"type":43,"tag":1565,"props":1876,"children":1877},{},[1878,1883,1885,1890,1892,1898,1900,1906,1908],{"type":43,"tag":79,"props":1879,"children":1880},{},[1881],{"type":49,"value":1882},"Caption pinned to bottom (字幕固定在视频下方)",{"type":49,"value":1884}," — every caption bar lives ONLY in ",{"type":43,"tag":58,"props":1886,"children":1888},{"className":1887},[],[1889],{"type":49,"value":964},{"type":49,"value":1891},"'s root track and is styled ",{"type":43,"tag":58,"props":1893,"children":1895},{"className":1894},[],[1896],{"type":49,"value":1897},"position:absolute; bottom:48px; left:50%; transform:translateX(-50%)",{"type":49,"value":1899},"; NEVER use ",{"type":43,"tag":58,"props":1901,"children":1903},{"className":1902},[],[1904],{"type":49,"value":1905},"top:",{"type":49,"value":1907},", and NEVER put a caption inside a scene composition (it would drift to the top\u002Fmiddle). Gate: ",{"type":43,"tag":58,"props":1909,"children":1911},{"className":1910},[],[1912],{"type":49,"value":1913},"scripts\u002Fcheck_caption_position.py",{"type":43,"tag":1565,"props":1915,"children":1916},{},[1917,1922],{"type":43,"tag":79,"props":1918,"children":1919},{},[1920],{"type":49,"value":1921},"Caption safe zone",{"type":49,"value":1923}," — every scene reserves the bottom ~180px (content in the top ~900px) so the subtitle never covers content (字幕遮挡)",{"type":43,"tag":1565,"props":1925,"children":1926},{},[1927,1932,1934,1940,1942,1948,1950,1956,1958],{"type":43,"tag":79,"props":1928,"children":1929},{},[1930],{"type":49,"value":1931},"Caption overflow",{"type":49,"value":1933}," — the caption bar is width-bounded (",{"type":43,"tag":58,"props":1935,"children":1937},{"className":1936},[],[1938],{"type":49,"value":1939},"max-width:1600px",{"type":49,"value":1941},", never ",{"type":43,"tag":58,"props":1943,"children":1945},{"className":1944},[],[1946],{"type":49,"value":1947},"white-space:nowrap",{"type":49,"value":1949},"), uses ",{"type":43,"tag":58,"props":1951,"children":1953},{"className":1952},[],[1954],{"type":49,"value":1955},"width:max-content",{"type":49,"value":1957}," (so it uses the full width before wrapping instead of shrinking to the ~960px half-frame), and each cue is short (split long sentences into sequential one-line cues); the subtitle never runs off the frame edge (字幕超出边界). Gate: ",{"type":43,"tag":58,"props":1959,"children":1961},{"className":1960},[],[1962],{"type":49,"value":1963},"scripts\u002Fcheck_caption_overflow.py",{"type":43,"tag":1565,"props":1965,"children":1966},{},[1967,1972,1974,1980,1981,1987,1989],{"type":43,"tag":79,"props":1968,"children":1969},{},[1970],{"type":49,"value":1971},"Caption always on top (字幕必须在最顶层)",{"type":49,"value":1973}," — the caption bar has an unbeatable ",{"type":43,"tag":58,"props":1975,"children":1977},{"className":1976},[],[1978],{"type":49,"value":1979},"z-index",{"type":49,"value":1098},{"type":43,"tag":58,"props":1982,"children":1984},{"className":1983},[],[1985],{"type":49,"value":1986},"2147483647",{"type":49,"value":1988},") so it is ALWAYS above every scene layer\u002Fpanel and can never be covered (字幕被遮挡); scenes must keep their z-index small (\u003C100). Gate: ",{"type":43,"tag":58,"props":1990,"children":1992},{"className":1991},[],[1993],{"type":49,"value":1963},{"type":43,"tag":1565,"props":1995,"children":1996},{},[1997,2002,2004,2010,2012,2018,2020],{"type":43,"tag":79,"props":1998,"children":1999},{},[2000],{"type":49,"value":2001},"Scene layout",{"type":49,"value":2003}," — ",{"type":43,"tag":58,"props":2005,"children":2007},{"className":2006},[],[2008],{"type":49,"value":2009},".scene-content",{"type":49,"value":2011}," is a centering box that fills the frame (",{"type":43,"tag":58,"props":2013,"children":2015},{"className":2014},[],[2016],{"type":49,"value":2017},"position:absolute;inset:0;display:flex;align-items:center;justify-content:center",{"type":49,"value":2019},"); filling alone (no flex-center trio) piles a single panel at the TOP with the bottom half empty (排版问题). Gate: ",{"type":43,"tag":58,"props":2021,"children":2023},{"className":2022},[],[2024],{"type":49,"value":2025},"scripts\u002Fcheck_scene_layout.py",{"type":43,"tag":1565,"props":2027,"children":2028},{},[2029,2034,2036,2042,2044,2050,2052,2058,2060],{"type":43,"tag":79,"props":2030,"children":2031},{},[2032],{"type":49,"value":2033},"No oversize\u002Foverflow (排版过大)",{"type":49,"value":2035}," — every element fits within 1920×1080 (content within the 1920×900 safe area with edge margins); no CSS ",{"type":43,"tag":58,"props":2037,"children":2039},{"className":2038},[],[2040],{"type":49,"value":2041},"width",{"type":49,"value":2043},">1920px \u002F ",{"type":43,"tag":58,"props":2045,"children":2047},{"className":2046},[],[2048],{"type":49,"value":2049},"height",{"type":49,"value":2051},">1080px, keep ",{"type":43,"tag":58,"props":2053,"children":2055},{"className":2054},[],[2056],{"type":49,"value":2057},"scale()",{"type":49,"value":2059}," ≤ ~1.5; size by container (%\u002Fmax-width\u002Fflex\u002Fgrid), scale SVG via viewBox. Hero text ≤ ~96px is guidance (decorative watermarks may be larger). Gate: ",{"type":43,"tag":58,"props":2061,"children":2063},{"className":2062},[],[2064],{"type":49,"value":2065},"scripts\u002Fcheck_scene_overflow.py",{"type":43,"tag":1565,"props":2067,"children":2068},{},[2069,2074,2076,2082,2084,2090,2092,2098,2100,2106,2108,2114,2115,2121,2123,2129,2131,2137,2139,2145],{"type":43,"tag":79,"props":2070,"children":2071},{},[2072],{"type":49,"value":2073},"SVG height must be bounded (SVG高度必须有界 — 防止场景突然变大)",{"type":49,"value":2075}," — never size a content ",{"type":43,"tag":58,"props":2077,"children":2079},{"className":2078},[],[2080],{"type":49,"value":2081},"\u003Csvg>",{"type":49,"value":2083}," as ",{"type":43,"tag":58,"props":2085,"children":2087},{"className":2086},[],[2088],{"type":49,"value":2089},"width:100%; height:auto",{"type":49,"value":2091}," with a tall\u002Fnear-square viewBox: ",{"type":43,"tag":58,"props":2093,"children":2095},{"className":2094},[],[2096],{"type":49,"value":2097},"height:auto",{"type":49,"value":2099}," ties the rendered height to the viewBox aspect ratio, so at full panel width a ",{"type":43,"tag":58,"props":2101,"children":2103},{"className":2102},[],[2104],{"type":49,"value":2105},"1000×760",{"type":49,"value":2107}," viewBox becomes ~1050px tall and overflows the ~860px usable height — the scene appears to \"suddenly get big\" (场景突然变大). Bound the height: give the SVG ",{"type":43,"tag":58,"props":2109,"children":2111},{"className":2110},[],[2112],{"type":49,"value":2113},"max-height:\u003Cusable>px",{"type":49,"value":1764},{"type":43,"tag":58,"props":2116,"children":2118},{"className":2117},[],[2119],{"type":49,"value":2120},"max-height:760px",{"type":49,"value":2122},"), or size it by height inside a bounded flex\u002Fgrid box with ",{"type":43,"tag":58,"props":2124,"children":2126},{"className":2125},[],[2127],{"type":49,"value":2128},"preserveAspectRatio=\"xMidYMid meet\"",{"type":49,"value":2130},", or choose a viewBox whose aspect ratio matches the available box. Deterministic gate: ",{"type":43,"tag":58,"props":2132,"children":2134},{"className":2133},[],[2135],{"type":49,"value":2136},"scripts\u002Fcheck_svg_height_bound.py",{"type":49,"value":2138},"; the headless-measured ",{"type":43,"tag":58,"props":2140,"children":2142},{"className":2141},[],[2143],{"type":49,"value":2144},"scripts\u002Fcheck_scene_fit.py",{"type":49,"value":2146}," is the runtime backstop.",{"type":43,"tag":1565,"props":2148,"children":2149},{},[2150,2155,2157,2162,2164,2169,2171,2177,2178,2184,2186],{"type":43,"tag":79,"props":2151,"children":2152},{},[2153],{"type":49,"value":2154},"No overlapping SVG labels (SVG标签禁止重叠)",{"type":49,"value":2156}," — inside a single ",{"type":43,"tag":58,"props":2158,"children":2160},{"className":2159},[],[2161],{"type":49,"value":2081},{"type":49,"value":2163},", no two ",{"type":43,"tag":58,"props":2165,"children":2167},{"className":2166},[],[2168],{"type":49,"value":1747},{"type":49,"value":2170}," labels may collide. Don't stack a fulcrum\u002Fcenter label and its arm\u002Fsegment labels on one shared line; put the center label above and arm labels below (anchored ",{"type":43,"tag":58,"props":2172,"children":2174},{"className":2173},[],[2175],{"type":49,"value":2176},"end",{"type":49,"value":1697},{"type":43,"tag":58,"props":2179,"children":2181},{"className":2180},[],[2182],{"type":49,"value":2183},"start",{"type":49,"value":2185},", pushed outward past the shape edge) with a gap of ≥ ~1 label-height; stagger point labels that share an axis and never let a point label bury an axis tick number (see Rule #28). Gate: ",{"type":43,"tag":58,"props":2187,"children":2189},{"className":2188},[],[2190],{"type":49,"value":2191},"scripts\u002Fcheck_svg_label_overlap.py",{"type":43,"tag":1565,"props":2193,"children":2194},{},[2195,2200,2202,2208,2210],{"type":43,"tag":79,"props":2196,"children":2197},{},[2198],{"type":49,"value":2199},"Node-graph diagrams clean (关系图\u002F流程图)",{"type":49,"value":2201}," — in a boxes+arrows+in-shape-label diagram: every in-shape label is in the same ",{"type":43,"tag":58,"props":2203,"children":2205},{"className":2204},[],[2206],{"type":49,"value":2207},"\u003Cg>",{"type":49,"value":2209}," as its shape (else it slides off → white字消失), white SVG text only ever sits inside a dark shape, arrow endpoints stop before the 方框 (no arrowhead poking into a box), edge labels stay in the gaps (see Rule #31). Gate: ",{"type":43,"tag":58,"props":2211,"children":2213},{"className":2212},[],[2214],{"type":49,"value":2215},"scripts\u002Fcheck_svg_node_graph.py",{"type":43,"tag":1565,"props":2217,"children":2218},{},[2219,2224,2225,2231,2232,2238,2240,2246],{"type":43,"tag":79,"props":2220,"children":2221},{},[2222],{"type":49,"value":2223},"No CSS-hidden content",{"type":49,"value":1681},{"type":43,"tag":58,"props":2226,"children":2228},{"className":2227},[],[2229],{"type":49,"value":2230},"opacity:0",{"type":49,"value":190},{"type":43,"tag":58,"props":2233,"children":2235},{"className":2234},[],[2236],{"type":49,"value":2237},"visibility:hidden",{"type":49,"value":2239}," in CSS; GSAP handles via ",{"type":43,"tag":58,"props":2241,"children":2243},{"className":2242},[],[2244],{"type":49,"value":2245},"autoAlpha:0",{"type":49,"value":2247}," in JS",{"type":43,"tag":1565,"props":2249,"children":2250},{},[2251,2256,2257,2262,2264],{"type":43,"tag":79,"props":2252,"children":2253},{},[2254],{"type":49,"value":2255},"No frosted glass",{"type":49,"value":1681},{"type":43,"tag":58,"props":2258,"children":2260},{"className":2259},[],[2261],{"type":49,"value":63},{"type":49,"value":2263},", panels are opaque ",{"type":43,"tag":58,"props":2265,"children":2267},{"className":2266},[],[2268],{"type":49,"value":2269},"#ffffff",{"type":43,"tag":1565,"props":2271,"children":2272},{},[2273,2278,2280,2286,2287],{"type":43,"tag":79,"props":2274,"children":2275},{},[2276],{"type":49,"value":2277},"Scene coverage",{"type":49,"value":2279}," — one composition per storyboard scene, all wired with ",{"type":43,"tag":58,"props":2281,"children":2283},{"className":2282},[],[2284],{"type":49,"value":2285},"data-composition-src",{"type":49,"value":1139},{"type":43,"tag":58,"props":2288,"children":2290},{"className":2289},[],[2291],{"type":49,"value":2292},"window.__timelines[...]",{"type":43,"tag":1565,"props":2294,"children":2295},{},[2296,2301,2302,2307,2309,2314,2316,2322,2323,2328],{"type":43,"tag":79,"props":2297,"children":2298},{},[2299],{"type":49,"value":2300},"Root refs",{"type":49,"value":2003},{"type":43,"tag":58,"props":2303,"children":2305},{"className":2304},[],[2306],{"type":49,"value":964},{"type":49,"value":2308}," uses ",{"type":43,"tag":58,"props":2310,"children":2312},{"className":2311},[],[2313],{"type":49,"value":2285},{"type":49,"value":2315}," (not ",{"type":43,"tag":58,"props":2317,"children":2319},{"className":2318},[],[2320],{"type":49,"value":2321},"data-src",{"type":49,"value":1723},{"type":43,"tag":58,"props":2324,"children":2326},{"className":2325},[],[2327],{"type":49,"value":1695},{"type":49,"value":2329},") for all scenes",{"type":43,"tag":1565,"props":2331,"children":2332},{},[2333,2338,2340,2346,2348,2354],{"type":43,"tag":79,"props":2334,"children":2335},{},[2336],{"type":49,"value":2337},"Geometry verification",{"type":49,"value":2339}," — every geometry scene (SVG with 3+ labeled points) has a ",{"type":43,"tag":58,"props":2341,"children":2343},{"className":2342},[],[2344],{"type":49,"value":2345},"\u003C!-- GEOMETRY VERIFICATION -->",{"type":49,"value":2347}," block; all ",{"type":43,"tag":58,"props":2349,"children":2351},{"className":2350},[],[2352],{"type":49,"value":2353},"ASSERT",{"type":49,"value":2355}," lines verified mathematically (coordinates satisfy parallel\u002Fperpendicular\u002Fmidpoint\u002Fintersection\u002Fratio constraints)",{"type":43,"tag":52,"props":2357,"children":2358},{},[2359],{"type":49,"value":2360},"Additional requirements NOT checked by precheck (manual verification):",{"type":43,"tag":1625,"props":2362,"children":2363},{},[2364,2382,2395,2400,2419],{"type":43,"tag":1565,"props":2365,"children":2366},{},[2367,2369,2374,2376],{"type":49,"value":2368},"Self-hosted fonts + KaTeX + GSAP copied into ",{"type":43,"tag":58,"props":2370,"children":2372},{"className":2371},[],[2373],{"type":49,"value":533},{"type":49,"value":2375}," AND mirrored into ",{"type":43,"tag":58,"props":2377,"children":2379},{"className":2378},[],[2380],{"type":49,"value":2381},"dist\u002Fcompositions\u002F",{"type":43,"tag":1565,"props":2383,"children":2384},{},[2385,2387,2393],{"type":49,"value":2386},"Chinese font NOT subsetted — one full ",{"type":43,"tag":58,"props":2388,"children":2390},{"className":2389},[],[2391],{"type":49,"value":2392},"NotoSansSC-Bold.woff2",{"type":49,"value":2394}," per weight",{"type":43,"tag":1565,"props":2396,"children":2397},{},[2398],{"type":49,"value":2399},"KaTeX CSS inlined with local font URLs",{"type":43,"tag":1565,"props":2401,"children":2402},{},[2403,2405,2411,2412,2418],{"type":49,"value":2404},"No gradient text on KaTeX formulas (use solid ",{"type":43,"tag":58,"props":2406,"children":2408},{"className":2407},[],[2409],{"type":49,"value":2410},"color",{"type":49,"value":1838},{"type":43,"tag":58,"props":2413,"children":2415},{"className":2414},[],[2416],{"type":49,"value":2417},"background-clip:text",{"type":49,"value":157},{"type":43,"tag":1565,"props":2420,"children":2421},{},[2422],{"type":49,"value":2423},"Grid\u002Flattice figures generated programmatically with deduped edges (not hand-typed coordinates)\n\n",{"type":43,"tag":67,"props":2425,"children":2427},{"id":2426},"step-6-compose-render",[2428],{"type":49,"value":2429},"Step 6: Compose & Render",{"type":43,"tag":52,"props":2431,"children":2432},{},[2433,2434,2438,2440,2446],{"type":49,"value":986},{"type":43,"tag":765,"props":2435,"children":2436},{"href":972},[2437],{"type":49,"value":972},{"type":49,"value":2439},". Invoke the ",{"type":43,"tag":58,"props":2441,"children":2443},{"className":2442},[],[2444],{"type":49,"value":2445},"hyperframes-cli",{"type":49,"value":2447}," skill for CLI commands.",{"type":43,"tag":52,"props":2449,"children":2450},{},[2451,2453,2459,2461],{"type":49,"value":2452},"Assemble root ",{"type":43,"tag":58,"props":2454,"children":2456},{"className":2455},[],[2457],{"type":49,"value":2458},"dist\u002Findex.html",{"type":49,"value":2460},", run lint + validate + inspect, preview, and render. ",{"type":43,"tag":79,"props":2462,"children":2463},{},[2464,2466,2471],{"type":49,"value":2465},"All CLI commands (lint, validate, render) must run from inside ",{"type":43,"tag":58,"props":2467,"children":2469},{"className":2468},[],[2470],{"type":49,"value":533},{"type":49,"value":301},{"type":43,"tag":52,"props":2473,"children":2474},{},[2475,2480,2481,2486,2488,2494,2496,2501,2503,2508,2510,2516,2518,2523,2525,2530,2532,2536],{"type":43,"tag":79,"props":2476,"children":2477},{},[2478],{"type":49,"value":2479},"🔎 MANDATORY after rendering — visual overlap self-check loop (渲染后抽帧自查，看到重叠必须改坐标重渲，直到不重叠):",{"type":49,"value":343},{"type":43,"tag":58,"props":2482,"children":2484},{"className":2483},[],[2485],{"type":49,"value":264},{"type":49,"value":2487}," now includes ",{"type":43,"tag":58,"props":2489,"children":2491},{"className":2490},[],[2492],{"type":49,"value":2493},"check_render_overlap.py",{"type":49,"value":2495},", a headless render-truth gate that measures REAL bounding boxes and FAILs when a ",{"type":43,"tag":58,"props":2497,"children":2499},{"className":2498},[],[2500],{"type":49,"value":1747},{"type":49,"value":2502}," is painted under\u002Fover a box it doesn't belong to (文字被方框覆盖\u002F压框 — e.g. 取食→取, 捕食→捕; static coordinate gates miss this because scaling + CJK glyph width + SVG paint order make a \"clear on paper\" label collide in the render). That gate must pass. ",{"type":43,"tag":79,"props":2504,"children":2505},{},[2506],{"type":49,"value":2507},"In addition, you MUST look at the pixels:",{"type":49,"value":2509}," for every scene with boxes\u002Fconnectors\u002Flabels\u002Fcharts, extract a settled frame (",{"type":43,"tag":58,"props":2511,"children":2513},{"className":2512},[],[2514],{"type":49,"value":2515},"ffmpeg -ss \u003Cscene_midpoint> -i output.mp4 -frames:v 1 \u002Ftmp\u002Fov.png",{"type":49,"value":2517},"), open it with the Read tool, and check by eye for ",{"type":43,"tag":79,"props":2519,"children":2520},{},[2521],{"type":49,"value":2522},"文字被框压\u002F裁切、框和线重叠、框对不齐，以及方向\u002F朝向\u002F语义错误",{"type":49,"value":2524},"（figure 画反\u002F朝向错：燃着木条火焰须在瓶内而非瓶口外、斜面朝向、箭头矢量方向、仪器插入端、倾倒口朝向、电池极性\u002F电流方向、图表是否真的体现所述趋势）. If you see ANY overlap\u002Fmisalignment\u002Fwrong-orientation, ",{"type":43,"tag":79,"props":2526,"children":2527},{},[2528],{"type":49,"value":2529},"edit the offending coordinates (move labels into the gap with clearance \u002F shorten connectors \u002F align boxes \u002F draw highlight rings inside the box \u002F flip the reversed figure), re-render, and look again — repeat until every frame is visually clean and physically correct.",{"type":49,"value":2531}," Never claim \"visually verified\" without having actually extracted and read the frames. Full loop in ",{"type":43,"tag":765,"props":2533,"children":2534},{"href":972},[2535],{"type":49,"value":972},{"type":49,"value":2537}," → \"Post-Render Visual Overlap Self-Check\".",{"type":43,"tag":52,"props":2539,"children":2540},{},[2541,2546,2548,2554,2556,2562,2564,2570,2572,2583,2585,2590,2592,2598,2600,2606,2608,2635,2637,2641],{"type":43,"tag":79,"props":2542,"children":2543},{},[2544],{"type":49,"value":2545},"🚫 MANDATORY after rendering — line\u002Fcurve render-truth gate (线\u002F曲线漏画检测):",{"type":49,"value":2547}," run ",{"type":43,"tag":58,"props":2549,"children":2551},{"className":2550},[],[2552],{"type":49,"value":2553},"python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fpostcheck.py\" dist",{"type":49,"value":2555}," and loop until it prints ",{"type":43,"tag":58,"props":2557,"children":2559},{"className":2558},[],[2560],{"type":49,"value":2561},"ALL POST-RENDER CHECKS PASSED",{"type":49,"value":2563},". Its ",{"type":43,"tag":58,"props":2565,"children":2567},{"className":2566},[],[2568],{"type":49,"value":2569},"check_curves_rendered.py",{"type":49,"value":2571}," is renderer-agnostic: it renders a KNOWN-GOOD reference of each scene (GSAP + fonts injected by absolute path), then checks that every solid line\u002Fcurve that scene should draw is ",{"type":43,"tag":79,"props":2573,"children":2574},{},[2575,2577],{"type":49,"value":2576},"actually painted in ",{"type":43,"tag":58,"props":2578,"children":2580},{"className":2579},[],[2581],{"type":49,"value":2582},"output.mp4",{"type":49,"value":2584},". This catches the most deceptive render bug — a graph that shows its dots\u002Faxes\u002Flabels but drops the connecting curves (",{"type":43,"tag":79,"props":2586,"children":2587},{},[2588],{"type":49,"value":2589},"\"有点、没线\"",{"type":49,"value":2591},") because JS-generated path ",{"type":43,"tag":58,"props":2593,"children":2595},{"className":2594},[],[2596],{"type":49,"value":2597},"d",{"type":49,"value":2599}," never ran (typically a hand-rolled renderer that stripped GSAP without re-injecting it by absolute ",{"type":43,"tag":58,"props":2601,"children":2603},{"className":2602},[],[2604],{"type":49,"value":2605},"file:\u002F\u002F",{"type":49,"value":2607},"). Pre-render gates cannot catch this — the composition is correct; only the rendered pixels reveal the loss. ",{"type":43,"tag":79,"props":2609,"children":2610},{},[2611,2613,2618,2620,2625,2627,2633],{"type":49,"value":2612},"Prevention: render with ",{"type":43,"tag":58,"props":2614,"children":2616},{"className":2615},[],[2617],{"type":49,"value":249},{"type":49,"value":2619},"; do NOT hand-roll a CDP\u002Fpuppeteer renderer — and if you must, re-inject GSAP\u002FKaTeX\u002Ffonts via absolute ",{"type":43,"tag":58,"props":2621,"children":2623},{"className":2622},[],[2624],{"type":49,"value":2605},{"type":49,"value":2626}," paths (as you already do for bg-texture), never strip ",{"type":43,"tag":58,"props":2628,"children":2630},{"className":2629},[],[2631],{"type":49,"value":2632},".\u002Fgsap\u002Fgsap.min.js",{"type":49,"value":2634}," and leave it.",{"type":49,"value":2636}," Full section in ",{"type":43,"tag":765,"props":2638,"children":2639},{"href":972},[2640],{"type":49,"value":972},{"type":49,"value":2642}," → \"Post-Render Line\u002FCurve Render-Truth Gate\".",{"type":43,"tag":52,"props":2644,"children":2645},{},[2646,2651,2653,2658,2660,2666,2668,2673,2674,2680,2682,2688,2690,2695,2697,2702,2704,2710,2712,2717,2719,2724,2726,2731,2733,2739,2741,2747],{"type":43,"tag":79,"props":2647,"children":2648},{},[2649],{"type":49,"value":2650},"🌊 MANDATORY after rendering — smooth-curve render-truth gate (曲线不能用折线逼近，看到折线必须改代码重渲，直到通过):",{"type":49,"value":2652}," the same ",{"type":43,"tag":58,"props":2654,"children":2656},{"className":2655},[],[2657],{"type":49,"value":257},{"type":49,"value":2659}," now also runs ",{"type":43,"tag":58,"props":2661,"children":2663},{"className":2662},[],[2664],{"type":49,"value":2665},"check_smooth_curve_render.py",{"type":49,"value":2667},". It loads each continuous-curve scene (抛物线\u002F双曲线\u002F正弦\u002F指数\u002F反比例衰减 …), seeks the animation to its settled state, then ",{"type":43,"tag":79,"props":2669,"children":2670},{},[2671],{"type":49,"value":2672},"walks the ACTUAL rendered curve geometry",{"type":49,"value":1098},{"type":43,"tag":58,"props":2675,"children":2677},{"className":2676},[],[2678],{"type":49,"value":2679},"getPointAtLength",{"type":49,"value":2681},") and FAILs when a curve is drawn as a handful of straight chords — a jagged zig-zag with sharp kinks and long dead-straight runs between them (",{"type":43,"tag":58,"props":2683,"children":2685},{"className":2684},[],[2686],{"type":49,"value":2687},"\u003Cpath d=\"M488,585 L500,540 L530,480 …\">",{"type":49,"value":2689}," 这类 ~7 段直线拼出来的\"曲线\"). This is a ",{"type":43,"tag":79,"props":2691,"children":2692},{},[2693],{"type":49,"value":2694},"visual\u002Frender-truth",{"type":49,"value":2696}," check: it sees the rendered shape, so it also catches curves whose ",{"type":43,"tag":58,"props":2698,"children":2700},{"className":2699},[],[2701],{"type":49,"value":2597},{"type":49,"value":2703}," is generated by JS (which the static pre-render ",{"type":43,"tag":58,"props":2705,"children":2707},{"className":2706},[],[2708],{"type":49,"value":2709},"check_smooth_curve.py",{"type":49,"value":2711}," cannot see). ",{"type":43,"tag":79,"props":2713,"children":2714},{},[2715],{"type":49,"value":2716},"When it FAILs, you MUST rewrite that curve's code and re-render, looping until it passes",{"type":49,"value":2718}," — do NOT ship a jagged curve. ",{"type":43,"tag":79,"props":2720,"children":2721},{},[2722],{"type":49,"value":2723},"Fix:",{"type":49,"value":2725}," build the path ",{"type":43,"tag":58,"props":2727,"children":2729},{"className":2728},[],[2730],{"type":49,"value":2597},{"type":49,"value":2732}," with the Catmull-Rom ",{"type":43,"tag":58,"props":2734,"children":2736},{"className":2735},[],[2737],{"type":49,"value":2738},"smoothPath()",{"type":49,"value":2740}," helper (sparse control points → cubic Béziers), OR emit a densely-sampled point list (~1 pt per ≤10 px, ~120+ pts across a wide axis). Genuinely piecewise-linear graphs (",{"type":43,"tag":58,"props":2742,"children":2744},{"className":2743},[],[2745],{"type":49,"value":2746},"y=|x|",{"type":49,"value":2748},", 分段函数, 折线统计图, 匀速距离-时间) are exempt. See references\u002Fstep-5-build-components.md → \"Smooth curves (顺滑曲线)\".",{"type":43,"tag":1014,"props":2750,"children":2751},{},[2752,2754,2773,2853,2859,2869,2875,2999,3005,8019,8025,8037,8124,8167,8466,8472],{"type":49,"value":2753},"\n**>>> Pre-render gate: precheck must have passed \u003C\u003C\u003C**\n",{"type":43,"tag":52,"props":2755,"children":2756},{},[2757,2759,2764,2766,2771],{"type":49,"value":2758},"Before rendering, confirm that ",{"type":43,"tag":58,"props":2760,"children":2762},{"className":2761},[],[2763],{"type":49,"value":1585},{"type":49,"value":2765}," was already run in Step 5 and printed ",{"type":43,"tag":58,"props":2767,"children":2769},{"className":2768},[],[2770],{"type":49,"value":1547},{"type":49,"value":2772},". If you skipped it or are unsure, run it now — it takes seconds. Do NOT render with failing prechecks.",{"type":43,"tag":52,"props":2774,"children":2775},{},[2776,2782,2783,2789,2791,2796,2798,2803,2805,2810,2812,2818,2820,2825,2827],{"type":43,"tag":58,"props":2777,"children":2779},{"className":2778},[],[2780],{"type":49,"value":2781},"npx hyperframes lint",{"type":49,"value":1139},{"type":43,"tag":58,"props":2784,"children":2786},{"className":2785},[],[2787],{"type":49,"value":2788},"npx hyperframes validate",{"type":49,"value":2790}," pass with zero errors (run from ",{"type":43,"tag":58,"props":2792,"children":2794},{"className":2793},[],[2795],{"type":49,"value":533},{"type":49,"value":2797},"). Video rendered to MP4 via ",{"type":43,"tag":58,"props":2799,"children":2801},{"className":2800},[],[2802],{"type":49,"value":249},{"type":49,"value":2804}," inside ",{"type":43,"tag":58,"props":2806,"children":2808},{"className":2807},[],[2809],{"type":49,"value":533},{"type":49,"value":2811},". The output ",{"type":43,"tag":58,"props":2813,"children":2815},{"className":2814},[],[2816],{"type":49,"value":2817},".mp4",{"type":49,"value":2819}," file must exist inside ",{"type":43,"tag":58,"props":2821,"children":2823},{"className":2822},[],[2824],{"type":49,"value":533},{"type":49,"value":2826}," and be reported to the user. Preview-only is NOT sufficient — rendering is mandatory. ",{"type":43,"tag":79,"props":2828,"children":2829},{},[2830,2832,2837,2839,2844,2846,2851],{"type":49,"value":2831},"After rendering: ",{"type":43,"tag":58,"props":2833,"children":2835},{"className":2834},[],[2836],{"type":49,"value":2493},{"type":49,"value":2838}," passes AND you have extracted+viewed a frame from every box\u002Fconnector\u002Flabel\u002Fchart scene and confirmed no clipped\u002Fcovered text, no line-into-box, no misaligned boxes — fixing coordinates and re-rendering until clean. AND ",{"type":43,"tag":58,"props":2840,"children":2842},{"className":2841},[],[2843],{"type":49,"value":2553},{"type":49,"value":2845}," prints ",{"type":43,"tag":58,"props":2847,"children":2849},{"className":2848},[],[2850],{"type":49,"value":2561},{"type":49,"value":2852}," (every line\u002Fcurve the scene should draw is actually painted in output.mp4 — 线\u002F曲线没漏画).",{"type":43,"tag":67,"props":2854,"children":2856},{"id":2855},"design-system",[2857],{"type":49,"value":2858},"Design System",{"type":43,"tag":52,"props":2860,"children":2861},{},[2862,2863,2867],{"type":49,"value":986},{"type":43,"tag":765,"props":2864,"children":2865},{"href":1170},[2866],{"type":49,"value":1170},{"type":49,"value":2868}," before writing ANY HTML. It defines the \"Aurora Scholar\" light-themed visual identity. Use its exact color tokens, font specs, and component styles. Do not invent colors.",{"type":43,"tag":67,"props":2870,"children":2872},{"id":2871},"problem-type-reference",[2873],{"type":49,"value":2874},"Problem Type Reference",{"type":43,"tag":102,"props":2876,"children":2877},{},[2878,2904],{"type":43,"tag":106,"props":2879,"children":2880},{},[2881],{"type":43,"tag":110,"props":2882,"children":2883},{},[2884,2889,2894,2899],{"type":43,"tag":114,"props":2885,"children":2886},{},[2887],{"type":49,"value":2888},"Problem Type",{"type":43,"tag":114,"props":2890,"children":2891},{},[2892],{"type":49,"value":2893},"Typical Duration",{"type":43,"tag":114,"props":2895,"children":2896},{},[2897],{"type":49,"value":2898},"Scenes",{"type":43,"tag":114,"props":2900,"children":2901},{},[2902],{"type":49,"value":2903},"Key Components",{"type":43,"tag":130,"props":2905,"children":2906},{},[2907,2930,2953,2976],{"type":43,"tag":110,"props":2908,"children":2909},{},[2910,2915,2920,2925],{"type":43,"tag":137,"props":2911,"children":2912},{},[2913],{"type":49,"value":2914},"Single equation",{"type":43,"tag":137,"props":2916,"children":2917},{},[2918],{"type":49,"value":2919},"30-60s",{"type":43,"tag":137,"props":2921,"children":2922},{},[2923],{"type":49,"value":2924},"4-5",{"type":43,"tag":137,"props":2926,"children":2927},{},[2928],{"type":49,"value":2929},"Problem Card + 2-3 Formula Panels + Conclusion",{"type":43,"tag":110,"props":2931,"children":2932},{},[2933,2938,2943,2948],{"type":43,"tag":137,"props":2934,"children":2935},{},[2936],{"type":49,"value":2937},"Multi-step algebra",{"type":43,"tag":137,"props":2939,"children":2940},{},[2941],{"type":49,"value":2942},"60-120s",{"type":43,"tag":137,"props":2944,"children":2945},{},[2946],{"type":49,"value":2947},"6-8",{"type":43,"tag":137,"props":2949,"children":2950},{},[2951],{"type":49,"value":2952},"Problem Card + Analysis + Steps + Summary",{"type":43,"tag":110,"props":2954,"children":2955},{},[2956,2961,2966,2971],{"type":43,"tag":137,"props":2957,"children":2958},{},[2959],{"type":49,"value":2960},"Geometry proof",{"type":43,"tag":137,"props":2962,"children":2963},{},[2964],{"type":49,"value":2965},"90-150s",{"type":43,"tag":137,"props":2967,"children":2968},{},[2969],{"type":49,"value":2970},"7-10",{"type":43,"tag":137,"props":2972,"children":2973},{},[2974],{"type":49,"value":2975},"Problem Card + Geometry Canvas + Proof Steps",{"type":43,"tag":110,"props":2977,"children":2978},{},[2979,2984,2989,2994],{"type":43,"tag":137,"props":2980,"children":2981},{},[2982],{"type":49,"value":2983},"Word problem",{"type":43,"tag":137,"props":2985,"children":2986},{},[2987],{"type":49,"value":2988},"60-90s",{"type":43,"tag":137,"props":2990,"children":2991},{},[2992],{"type":49,"value":2993},"5-7",{"type":43,"tag":137,"props":2995,"children":2996},{},[2997],{"type":49,"value":2998},"Problem Card + Modeling + Solve + Verify",{"type":43,"tag":67,"props":3000,"children":3002},{"id":3001},"non-negotiable-rules",[3003],{"type":49,"value":3004},"Non-Negotiable Rules",{"type":43,"tag":1561,"props":3006,"children":3007},{},[3008,3033,3125,3135,3145,3205,3215,3240,3295,3364,3380,3390,3865,3973,4250,4369,4512,4721,4846,4979,5078,5146,5201,5312,5407,6009,6714,6850,7049,7213,7327,7472,7672],{"type":43,"tag":1565,"props":3009,"children":3010},{},[3011,3016,3018,3023,3025,3031],{"type":43,"tag":79,"props":3012,"children":3013},{},[3014],{"type":49,"value":3015},"Caption text from original script (字幕必须用原始脚本文本).",{"type":49,"value":3017}," Video captions\u002Fsubtitles MUST use text from ",{"type":43,"tag":58,"props":3019,"children":3021},{"className":3020},[],[3022],{"type":49,"value":869},{"type":49,"value":3024}," (which contains the original narration script text with timestamps measured from TTS output). The timestamps are 100% accurate because they are measured from each sentence's actual TTS audio duration during sentence-by-sentence synthesis. The OpenCC ",{"type":43,"tag":58,"props":3026,"children":3028},{"className":3027},[],[3029],{"type":49,"value":3030},"t2s",{"type":49,"value":3032}," conversion is no longer needed — transcript text comes directly from the original script.",{"type":43,"tag":1565,"props":3034,"children":3035},{},[3036,3041,3043,3049,3051,3057,3059,3064,3066,3072,3073,3079,3081,3086,3088,3093,3095,3100,3102,3107,3109,3115,3117,3123],{"type":43,"tag":79,"props":3037,"children":3038},{},[3039],{"type":49,"value":3040},"KaTeX for layout-dependent math; plain HTML\u002FChinese for simple symbols (复杂公式用KaTeX，简单符号用HTML或中文).",{"type":49,"value":3042}," Use KaTeX only when the expression needs math layout features (fractions, roots, summations, matrices). For simple comparisons and standalone symbols (≤, ≥, °, ×), prefer HTML entities or Chinese text directly — e.g., write ",{"type":43,"tag":58,"props":3044,"children":3046},{"className":3045},[],[3047],{"type":49,"value":3048},"\u003Cspan>OP' ≤ 1\u003C\u002Fspan>",{"type":49,"value":3050}," not ",{"type":43,"tag":58,"props":3052,"children":3054},{"className":3053},[],[3055],{"type":49,"value":3056},"katex.render(\"OP' \\\\leq 1\", ...)",{"type":49,"value":3058}," — this avoids the fragile JS backslash-escaping pipeline entirely (see Rule #25). Never display raw LaTeX source code. ",{"type":43,"tag":79,"props":3060,"children":3061},{},[3062],{"type":49,"value":3063},"When you DO use KaTeX in JS strings, ALWAYS double-escape LaTeX backslashes",{"type":49,"value":3065}," — write ",{"type":43,"tag":58,"props":3067,"children":3069},{"className":3068},[],[3070],{"type":49,"value":3071},"\"\\\\\\\\dfrac{1}{2}\"",{"type":49,"value":3050},{"type":43,"tag":58,"props":3074,"children":3076},{"className":3075},[],[3077],{"type":49,"value":3078},"\"\\\\dfrac{1}{2}\"",{"type":49,"value":3080},". The ",{"type":43,"tag":58,"props":3082,"children":3084},{"className":3083},[],[3085],{"type":49,"value":1852},{"type":49,"value":3087}," escape is silent: JS turns ",{"type":43,"tag":58,"props":3089,"children":3091},{"className":3090},[],[3092],{"type":49,"value":1852},{"type":49,"value":3094}," → ",{"type":43,"tag":58,"props":3096,"children":3098},{"className":3097},[],[3099],{"type":49,"value":2597},{"type":49,"value":3101},", so ",{"type":43,"tag":58,"props":3103,"children":3105},{"className":3104},[],[3106],{"type":49,"value":1844},{"type":49,"value":3108}," becomes ",{"type":43,"tag":58,"props":3110,"children":3112},{"className":3111},[],[3113],{"type":49,"value":3114},"dfrac",{"type":49,"value":3116}," and KaTeX renders italic text \"dfrac12\" instead of a fraction. This is the single most common rendering bug. After building all compositions, run ",{"type":43,"tag":58,"props":3118,"children":3120},{"className":3119},[],[3121],{"type":49,"value":3122},"python3 \"$EDU_SKILL_ROOT\u002Fscripts\u002Fcheck_katex_escaping.py\" dist",{"type":49,"value":3124}," and fix every reported line BEFORE proceeding to Step 6.",{"type":43,"tag":1565,"props":3126,"children":3127},{},[3128,3133],{"type":43,"tag":79,"props":3129,"children":3130},{},[3131],{"type":49,"value":3132},"Chinese pacing.",{"type":49,"value":3134}," Narration at 3.5-4.0 characters\u002Fsecond. Leave 0.5-1.0s pauses between steps.",{"type":43,"tag":1565,"props":3136,"children":3137},{},[3138,3143],{"type":43,"tag":79,"props":3139,"children":3140},{},[3141],{"type":49,"value":3142},"Three layers per scene.",{"type":49,"value":3144}," Background treatment (wave texture + aurora mesh orbs) + content layer + accent elements. No flat single-layer scenes. See design-system.md \"Background Treatment\" for the exact CSS pattern and aurora palette guide.",{"type":43,"tag":1565,"props":3146,"children":3147},{},[3148,3153,3155,3161,3163,3168,3170,3175,3176,3182,3184,3188,3190,3196,3198,3203],{"type":43,"tag":79,"props":3149,"children":3150},{},[3151],{"type":49,"value":3152},"Solid opaque panels — frosted glass is FORBIDDEN (禁止毛玻璃，完全避免遮挡).",{"type":49,"value":3154}," All content panels use the solid panel style from design-system.md — OPAQUE ",{"type":43,"tag":58,"props":3156,"children":3158},{"className":3157},[],[3159],{"type":49,"value":3160},"background:#ffffff",{"type":49,"value":3162}," (or white alpha ≥ 0.92), depth from borders + layered box-shadow + inset top highlight. ",{"type":43,"tag":79,"props":3164,"children":3165},{},[3166],{"type":49,"value":3167},"NEVER",{"type":49,"value":3169}," use ",{"type":43,"tag":58,"props":3171,"children":3173},{"className":3172},[],[3174],{"type":49,"value":63},{"type":49,"value":190},{"type":43,"tag":58,"props":3177,"children":3179},{"className":3178},[],[3180],{"type":49,"value":3181},"-webkit-backdrop-filter",{"type":49,"value":3183},", and ",{"type":43,"tag":79,"props":3185,"children":3186},{},[3187],{"type":49,"value":3167},{"type":49,"value":3189}," a see-through translucent panel background — a blurred\u002Fsee-through panel washes out and OCCLUDES the problem text \u002F diagram \u002F formulas behind it (this is a hard defect). Enforced by ",{"type":43,"tag":58,"props":3191,"children":3193},{"className":3192},[],[3194],{"type":49,"value":3195},"scripts\u002Fcheck_no_glass.py",{"type":49,"value":3197}," (in ",{"type":43,"tag":58,"props":3199,"children":3201},{"className":3200},[],[3202],{"type":49,"value":264},{"type":49,"value":3204},"): render is blocked if any glass\u002Ftranslucent panel is found.",{"type":43,"tag":1565,"props":3206,"children":3207},{},[3208,3213],{"type":43,"tag":79,"props":3209,"children":3210},{},[3211],{"type":49,"value":3212},"SVG geometry.",{"type":49,"value":3214}," Geometric figures use SVG path drawing animation, never static images.",{"type":43,"tag":1565,"props":3216,"children":3217},{},[3218,3223,3225,3231,3232,3238],{"type":43,"tag":79,"props":3219,"children":3220},{},[3221],{"type":49,"value":3222},"Deterministic.",{"type":49,"value":3224}," No ",{"type":43,"tag":58,"props":3226,"children":3228},{"className":3227},[],[3229],{"type":49,"value":3230},"Math.random()",{"type":49,"value":455},{"type":43,"tag":58,"props":3233,"children":3235},{"className":3234},[],[3236],{"type":49,"value":3237},"Date.now()",{"type":49,"value":3239},", or async timeline construction.",{"type":43,"tag":1565,"props":3241,"children":3242},{},[3243,3248,3250,3255,3257,3262,3264,3268,3269,3274,3275,3280,3282,3287,3289,3293],{"type":43,"tag":79,"props":3244,"children":3245},{},[3246],{"type":49,"value":3247},"Delegate.",{"type":49,"value":3249}," Use the ",{"type":43,"tag":58,"props":3251,"children":3253},{"className":3252},[],[3254],{"type":49,"value":1231},{"type":49,"value":3256}," skill for composition rules, ",{"type":43,"tag":58,"props":3258,"children":3260},{"className":3259},[],[3261],{"type":49,"value":2445},{"type":49,"value":3263}," for CLI commands. TTS uses ",{"type":43,"tag":79,"props":3265,"children":3266},{},[3267],{"type":49,"value":1096},{"type":49,"value":1098},{"type":43,"tag":58,"props":3270,"children":3272},{"className":3271},[],[3273],{"type":49,"value":1104},{"type":49,"value":1106},{"type":43,"tag":58,"props":3276,"children":3278},{"className":3277},[],[3279],{"type":49,"value":443},{"type":49,"value":3281},", HTTP) — no self-hosted node, no URL wiring; needs ",{"type":43,"tag":58,"props":3283,"children":3285},{"className":3284},[],[3286],{"type":49,"value":432},{"type":49,"value":3288},". Synthesize sentences ",{"type":43,"tag":79,"props":3290,"children":3291},{},[3292],{"type":49,"value":1125},{"type":49,"value":3294}," (thread pool) for speed, and take per-sentence timestamps from each returned clip's measured duration (see step-3-voice-generation.md). This skill defines the math-tutorial domain logic only.",{"type":43,"tag":1565,"props":3296,"children":3297},{},[3298,3303,3305,3311,3313,3319,3320,3326,3327,3333,3334,3340,3342,3348,3349,3355,3356,3362],{"type":43,"tag":79,"props":3299,"children":3300},{},[3301],{"type":49,"value":3302},"Dark text on light backgrounds (浅色背景深色文字).",{"type":49,"value":3304}," This is a light-theme design system. All text — KaTeX equations, SVG labels, Chinese body text, HTML table cells — MUST use dark colors (",{"type":43,"tag":58,"props":3306,"children":3308},{"className":3307},[],[3309],{"type":49,"value":3310},"#0f172a",{"type":49,"value":3312}," or darker). Every composition MUST include the full \"Mandatory Global Color Reset\" block from design-system.md: root selector with ",{"type":43,"tag":58,"props":3314,"children":3316},{"className":3315},[],[3317],{"type":49,"value":3318},"color: #0f172a",{"type":49,"value":455},{"type":43,"tag":58,"props":3321,"children":3323},{"className":3322},[],[3324],{"type":49,"value":3325},".katex, .katex * { color: #0f172a; }",{"type":49,"value":455},{"type":43,"tag":58,"props":3328,"children":3330},{"className":3329},[],[3331],{"type":49,"value":3332},".katex-mathml { display: none !important; }",{"type":49,"value":3183},{"type":43,"tag":58,"props":3335,"children":3337},{"className":3336},[],[3338],{"type":49,"value":3339},"table, th, td { color: #0f172a; }",{"type":49,"value":3341},". Never use ",{"type":43,"tag":58,"props":3343,"children":3345},{"className":3344},[],[3346],{"type":49,"value":3347},"#fff",{"type":49,"value":455},{"type":43,"tag":58,"props":3350,"children":3352},{"className":3351},[],[3353],{"type":49,"value":3354},"#f8fafc",{"type":49,"value":455},{"type":43,"tag":58,"props":3357,"children":3359},{"className":3358},[],[3360],{"type":49,"value":3361},"#e8ecf4",{"type":49,"value":3363},", or any light color for text on the light background.",{"type":43,"tag":1565,"props":3365,"children":3366},{},[3367,3372,3374,3378],{"type":43,"tag":79,"props":3368,"children":3369},{},[3370],{"type":49,"value":3371},"Pre-built assets first.",{"type":49,"value":3373}," Before writing custom HTML\u002FCSS for any visual object (car, train, candle, battery, lens, etc.), check ",{"type":43,"tag":765,"props":3375,"children":3376},{"href":1190},[3377],{"type":49,"value":1190},{"type":49,"value":3379},". If a matching component exists, copy its CSS and HTML verbatim. If NO match exists, follow the \"Quality Fallback Template\" in ASSET_CATALOG.md — every custom object must have ≥3 gradient layers, inset shadows, ground shadow, glow halo, and no CSS @keyframes. Compare against the candle component for quality level.",{"type":43,"tag":1565,"props":3381,"children":3382},{},[3383,3388],{"type":43,"tag":79,"props":3384,"children":3385},{},[3386],{"type":49,"value":3387},"No emoji.",{"type":49,"value":3389}," Never use emoji characters (🔧⚙️🔵🔴🟢⭕📐✅❌⚡💡 etc.) anywhere in visible text — titles, labels, captions, formula notes, phase titles, badge text, or any string rendered in the video. Headless Chromium has no emoji font installed; all emoji render as □ (tofu boxes) in the final video. Use plain Chinese text or SVG\u002FCSS shapes instead.",{"type":43,"tag":1565,"props":3391,"children":3392},{},[3393,3398,3400,3406,3408,3414,3416,3422,3424,3430,3432,3438,3440,3446,3448,3454,3456,3462,3464,3470,3472,3477,3479,3485,3487,3492,3494,3499,3501,3507,3508,3514,3516,3522,3523,3529,3531,3544,3546,3552,3554],{"type":43,"tag":79,"props":3394,"children":3395},{},[3396],{"type":49,"value":3397},"Math symbols: simple via HTML entities, complex via KaTeX — NO Chinese inside KaTeX (简单符号用HTML实体，复杂公式用KaTeX，KaTeX禁止包含中文).",{"type":49,"value":3399}," Common math comparison and operator symbols are safe as HTML entities in Noto Sans SC \u002F Inter: ≤ (",{"type":43,"tag":58,"props":3401,"children":3403},{"className":3402},[],[3404],{"type":49,"value":3405},"&le;",{"type":49,"value":3407},"), ≥ (",{"type":43,"tag":58,"props":3409,"children":3411},{"className":3410},[],[3412],{"type":49,"value":3413},"&ge;",{"type":49,"value":3415},"), ≠ (",{"type":43,"tag":58,"props":3417,"children":3419},{"className":3418},[],[3420],{"type":49,"value":3421},"&ne;",{"type":49,"value":3423},"), ° (",{"type":43,"tag":58,"props":3425,"children":3427},{"className":3426},[],[3428],{"type":49,"value":3429},"&deg;",{"type":49,"value":3431},"), × (",{"type":43,"tag":58,"props":3433,"children":3435},{"className":3434},[],[3436],{"type":49,"value":3437},"&times;",{"type":49,"value":3439},"), ÷ (",{"type":43,"tag":58,"props":3441,"children":3443},{"className":3442},[],[3444],{"type":49,"value":3445},"&divide;",{"type":49,"value":3447},"), ± (",{"type":43,"tag":58,"props":3449,"children":3451},{"className":3450},[],[3452],{"type":49,"value":3453},"&plusmn;",{"type":49,"value":3455},"), ² (",{"type":43,"tag":58,"props":3457,"children":3459},{"className":3458},[],[3460],{"type":49,"value":3461},"&sup2;",{"type":49,"value":3463},"), ³ (",{"type":43,"tag":58,"props":3465,"children":3467},{"className":3466},[],[3468],{"type":49,"value":3469},"&sup3;",{"type":49,"value":3471},") — use these directly in HTML text instead of KaTeX when no layout structure is needed (see Rule #25). ",{"type":43,"tag":79,"props":3473,"children":3474},{},[3475],{"type":49,"value":3476},"Greek letters (α, β, γ, δ, ε, θ, λ, μ, π, φ, ω) are safe as Unicode characters in HTML text",{"type":49,"value":3478}," — Inter-Variable.woff2 contains all Greek glyphs (Noto Sans SC does NOT). The font-family stack ",{"type":43,"tag":58,"props":3480,"children":3482},{"className":3481},[],[3483],{"type":49,"value":3484},"\"Noto Sans SC\", Inter, sans-serif",{"type":49,"value":3486}," will fall back to Inter for Greek. ",{"type":43,"tag":79,"props":3488,"children":3489},{},[3490],{"type":49,"value":3491},"NEVER write the English word",{"type":49,"value":3493}," (\"alpha\", \"beta\", \"theta\") ",{"type":43,"tag":79,"props":3495,"children":3496},{},[3497],{"type":49,"value":3498},"— always use the Unicode character",{"type":49,"value":3500}," (α, β, θ). Do NOT insert other specialized Unicode math symbols (∠, △, ⊥, ∥, √, ∞, ₁, ₂, etc.) directly into HTML text — use KaTeX for these in formulas, or Chinese equivalents in non-formula context (\"角ABC\" not \"∠ABC\", \"三角形\" not \"△\"). For inline subscripts\u002Fsuperscripts in labels, use HTML ",{"type":43,"tag":58,"props":3502,"children":3504},{"className":3503},[],[3505],{"type":49,"value":3506},"\u003Csub>",{"type":49,"value":1697},{"type":43,"tag":58,"props":3509,"children":3511},{"className":3510},[],[3512],{"type":49,"value":3513},"\u003Csup>",{"type":49,"value":3515}," tags (e.g., ",{"type":43,"tag":58,"props":3517,"children":3519},{"className":3518},[],[3520],{"type":49,"value":3521},"F\u003Csub>1\u003C\u002Fsub>",{"type":49,"value":3050},{"type":43,"tag":58,"props":3524,"children":3526},{"className":3525},[],[3527],{"type":49,"value":3528},"F₁",{"type":49,"value":3530},"). The only safe non-ASCII characters in plain text are standard CJK Unified Ideographs (U+4E00–U+9FFF), common CJK punctuation, the HTML-entity math symbols listed above, and Greek letters (U+0370–U+03FF). ",{"type":43,"tag":79,"props":3532,"children":3533},{},[3534,3536,3542],{"type":49,"value":3535},"CRITICAL: Never put Chinese text inside KaTeX ",{"type":43,"tag":58,"props":3537,"children":3539},{"className":3538},[],[3540],{"type":49,"value":3541},"\\text{...}",{"type":49,"value":3543}," or any KaTeX command.",{"type":49,"value":3545}," KaTeX's math fonts (KaTeX_Main, KaTeX_Math, etc.) do NOT contain CJK glyphs — any Chinese character inside ",{"type":43,"tag":58,"props":3547,"children":3549},{"className":3548},[],[3550],{"type":49,"value":3551},"\\text{}",{"type":49,"value":3553}," renders as □ tofu boxes. When a formula needs to be mixed with Chinese text, break it into separate HTML elements:\n",{"type":43,"tag":571,"props":3555,"children":3559},{"className":3556,"code":3557,"language":3558,"meta":576,"style":576},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- WRONG — Chinese in \\text{} renders as □□□ -->\n\u003Cspan id=\"eq\" data-tex=\"n \\text{ 还是 } V\">\u003C\u002Fspan>\n\n\u003C!-- CORRECT — Chinese in HTML, math in KaTeX -->\n\u003Cspan>判断操作对 \u003C\u002Fspan>\u003Cspan id=\"eq-n\" data-tex=\"n\">\u003C\u002Fspan>\u003Cspan> 或 \u003C\u002Fspan>\u003Cspan id=\"eq-v\" data-tex=\"V\">\u003C\u002Fspan>\u003Cspan> 的影响\u003C\u002Fspan>\n","html",[3560],{"type":43,"tag":58,"props":3561,"children":3562},{"__ignoreMap":576},[3563,3572,3644,3654,3663],{"type":43,"tag":582,"props":3564,"children":3565},{"class":584,"line":585},[3566],{"type":43,"tag":582,"props":3567,"children":3569},{"style":3568},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[3570],{"type":49,"value":3571},"\u003C!-- WRONG — Chinese in \\text{} renders as □□□ -->\n",{"type":43,"tag":582,"props":3573,"children":3574},{"class":584,"line":616},[3575,3580,3585,3591,3595,3599,3604,3608,3613,3617,3621,3626,3630,3635,3639],{"type":43,"tag":582,"props":3576,"children":3577},{"style":594},[3578],{"type":49,"value":3579},"\u003C",{"type":43,"tag":582,"props":3581,"children":3583},{"style":3582},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[3584],{"type":49,"value":582},{"type":43,"tag":582,"props":3586,"children":3588},{"style":3587},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[3589],{"type":49,"value":3590}," id",{"type":43,"tag":582,"props":3592,"children":3593},{"style":594},[3594],{"type":49,"value":597},{"type":43,"tag":582,"props":3596,"children":3597},{"style":594},[3598],{"type":49,"value":602},{"type":43,"tag":582,"props":3600,"children":3601},{"style":605},[3602],{"type":49,"value":3603},"eq",{"type":43,"tag":582,"props":3605,"children":3606},{"style":594},[3607],{"type":49,"value":602},{"type":43,"tag":582,"props":3609,"children":3610},{"style":3587},[3611],{"type":49,"value":3612}," data-tex",{"type":43,"tag":582,"props":3614,"children":3615},{"style":594},[3616],{"type":49,"value":597},{"type":43,"tag":582,"props":3618,"children":3619},{"style":594},[3620],{"type":49,"value":602},{"type":43,"tag":582,"props":3622,"children":3623},{"style":605},[3624],{"type":49,"value":3625},"n \\text{ 还是 } V",{"type":43,"tag":582,"props":3627,"children":3628},{"style":594},[3629],{"type":49,"value":602},{"type":43,"tag":582,"props":3631,"children":3632},{"style":594},[3633],{"type":49,"value":3634},">\u003C\u002F",{"type":43,"tag":582,"props":3636,"children":3637},{"style":3582},[3638],{"type":49,"value":582},{"type":43,"tag":582,"props":3640,"children":3641},{"style":594},[3642],{"type":49,"value":3643},">\n",{"type":43,"tag":582,"props":3645,"children":3647},{"class":584,"line":3646},3,[3648],{"type":43,"tag":582,"props":3649,"children":3651},{"emptyLinePlaceholder":3650},true,[3652],{"type":49,"value":3653},"\n",{"type":43,"tag":582,"props":3655,"children":3657},{"class":584,"line":3656},4,[3658],{"type":43,"tag":582,"props":3659,"children":3660},{"style":3568},[3661],{"type":49,"value":3662},"\u003C!-- CORRECT — Chinese in HTML, math in KaTeX -->\n",{"type":43,"tag":582,"props":3664,"children":3666},{"class":584,"line":3665},5,[3667,3671,3675,3680,3685,3690,3694,3699,3703,3707,3711,3715,3720,3724,3728,3732,3736,3741,3745,3749,3753,3757,3761,3765,3770,3774,3778,3782,3786,3790,3794,3798,3803,3807,3811,3815,3819,3824,3828,3832,3836,3840,3844,3848,3853,3857,3861],{"type":43,"tag":582,"props":3668,"children":3669},{"style":594},[3670],{"type":49,"value":3579},{"type":43,"tag":582,"props":3672,"children":3673},{"style":3582},[3674],{"type":49,"value":582},{"type":43,"tag":582,"props":3676,"children":3677},{"style":594},[3678],{"type":49,"value":3679},">",{"type":43,"tag":582,"props":3681,"children":3682},{"style":589},[3683],{"type":49,"value":3684},"判断操作对 ",{"type":43,"tag":582,"props":3686,"children":3687},{"style":594},[3688],{"type":49,"value":3689},"\u003C\u002F",{"type":43,"tag":582,"props":3691,"children":3692},{"style":3582},[3693],{"type":49,"value":582},{"type":43,"tag":582,"props":3695,"children":3696},{"style":594},[3697],{"type":49,"value":3698},">\u003C",{"type":43,"tag":582,"props":3700,"children":3701},{"style":3582},[3702],{"type":49,"value":582},{"type":43,"tag":582,"props":3704,"children":3705},{"style":3587},[3706],{"type":49,"value":3590},{"type":43,"tag":582,"props":3708,"children":3709},{"style":594},[3710],{"type":49,"value":597},{"type":43,"tag":582,"props":3712,"children":3713},{"style":594},[3714],{"type":49,"value":602},{"type":43,"tag":582,"props":3716,"children":3717},{"style":605},[3718],{"type":49,"value":3719},"eq-n",{"type":43,"tag":582,"props":3721,"children":3722},{"style":594},[3723],{"type":49,"value":602},{"type":43,"tag":582,"props":3725,"children":3726},{"style":3587},[3727],{"type":49,"value":3612},{"type":43,"tag":582,"props":3729,"children":3730},{"style":594},[3731],{"type":49,"value":597},{"type":43,"tag":582,"props":3733,"children":3734},{"style":594},[3735],{"type":49,"value":602},{"type":43,"tag":582,"props":3737,"children":3738},{"style":605},[3739],{"type":49,"value":3740},"n",{"type":43,"tag":582,"props":3742,"children":3743},{"style":594},[3744],{"type":49,"value":602},{"type":43,"tag":582,"props":3746,"children":3747},{"style":594},[3748],{"type":49,"value":3634},{"type":43,"tag":582,"props":3750,"children":3751},{"style":3582},[3752],{"type":49,"value":582},{"type":43,"tag":582,"props":3754,"children":3755},{"style":594},[3756],{"type":49,"value":3698},{"type":43,"tag":582,"props":3758,"children":3759},{"style":3582},[3760],{"type":49,"value":582},{"type":43,"tag":582,"props":3762,"children":3763},{"style":594},[3764],{"type":49,"value":3679},{"type":43,"tag":582,"props":3766,"children":3767},{"style":589},[3768],{"type":49,"value":3769}," 或 ",{"type":43,"tag":582,"props":3771,"children":3772},{"style":594},[3773],{"type":49,"value":3689},{"type":43,"tag":582,"props":3775,"children":3776},{"style":3582},[3777],{"type":49,"value":582},{"type":43,"tag":582,"props":3779,"children":3780},{"style":594},[3781],{"type":49,"value":3698},{"type":43,"tag":582,"props":3783,"children":3784},{"style":3582},[3785],{"type":49,"value":582},{"type":43,"tag":582,"props":3787,"children":3788},{"style":3587},[3789],{"type":49,"value":3590},{"type":43,"tag":582,"props":3791,"children":3792},{"style":594},[3793],{"type":49,"value":597},{"type":43,"tag":582,"props":3795,"children":3796},{"style":594},[3797],{"type":49,"value":602},{"type":43,"tag":582,"props":3799,"children":3800},{"style":605},[3801],{"type":49,"value":3802},"eq-v",{"type":43,"tag":582,"props":3804,"children":3805},{"style":594},[3806],{"type":49,"value":602},{"type":43,"tag":582,"props":3808,"children":3809},{"style":3587},[3810],{"type":49,"value":3612},{"type":43,"tag":582,"props":3812,"children":3813},{"style":594},[3814],{"type":49,"value":597},{"type":43,"tag":582,"props":3816,"children":3817},{"style":594},[3818],{"type":49,"value":602},{"type":43,"tag":582,"props":3820,"children":3821},{"style":605},[3822],{"type":49,"value":3823},"V",{"type":43,"tag":582,"props":3825,"children":3826},{"style":594},[3827],{"type":49,"value":602},{"type":43,"tag":582,"props":3829,"children":3830},{"style":594},[3831],{"type":49,"value":3634},{"type":43,"tag":582,"props":3833,"children":3834},{"style":3582},[3835],{"type":49,"value":582},{"type":43,"tag":582,"props":3837,"children":3838},{"style":594},[3839],{"type":49,"value":3698},{"type":43,"tag":582,"props":3841,"children":3842},{"style":3582},[3843],{"type":49,"value":582},{"type":43,"tag":582,"props":3845,"children":3846},{"style":594},[3847],{"type":49,"value":3679},{"type":43,"tag":582,"props":3849,"children":3850},{"style":589},[3851],{"type":49,"value":3852}," 的影响",{"type":43,"tag":582,"props":3854,"children":3855},{"style":594},[3856],{"type":49,"value":3689},{"type":43,"tag":582,"props":3858,"children":3859},{"style":3582},[3860],{"type":49,"value":582},{"type":43,"tag":582,"props":3862,"children":3863},{"style":594},[3864],{"type":49,"value":3643},{"type":43,"tag":1565,"props":3866,"children":3867},{},[3868,3873,3875,3881,3883,3889,3891,3896,3897,3903,3905,3911,3913,3919,3921,3926,3928,3934,3936,3942,3944,3949,3951,3957,3959,3964,3966,3971],{"type":43,"tag":79,"props":3869,"children":3870},{},[3871],{"type":49,"value":3872},"KaTeX CSS must be inlined, fonts self-hosted (字幕和公式的CSS必须内联，字体离线).",{"type":49,"value":3874}," The HyperFrames compiler processes CDN ",{"type":43,"tag":58,"props":3876,"children":3878},{"className":3877},[],[3879],{"type":49,"value":3880},"\u003Clink rel=\"stylesheet\">",{"type":49,"value":3882}," tags by extracting ONLY ",{"type":43,"tag":58,"props":3884,"children":3886},{"className":3885},[],[3887],{"type":49,"value":3888},"@font-face",{"type":49,"value":3890}," rules and discarding all other CSS. This silently breaks KaTeX layout (fractions, subscripts, spacing render as flat plain text). ",{"type":43,"tag":79,"props":3892,"children":3893},{},[3894],{"type":49,"value":3895},"Never",{"type":49,"value":3169},{"type":43,"tag":58,"props":3898,"children":3900},{"className":3899},[],[3901],{"type":49,"value":3902},"\u003Clink rel=\"stylesheet\" href=\"...katex.min.css\">",{"type":49,"value":3904},". Instead: copy the shipped ",{"type":43,"tag":58,"props":3906,"children":3908},{"className":3907},[],[3909],{"type":49,"value":3910},"assets\u002Fkatex\u002Fkatex.min.css",{"type":49,"value":3912}," into ",{"type":43,"tag":58,"props":3914,"children":3916},{"className":3915},[],[3917],{"type":49,"value":3918},"dist\u002Fkatex\u002F",{"type":49,"value":3920},", rewrite its font URLs to the ",{"type":43,"tag":79,"props":3922,"children":3923},{},[3924],{"type":49,"value":3925},"local",{"type":49,"value":3927}," self-hosted path (",{"type":43,"tag":58,"props":3929,"children":3931},{"className":3930},[],[3932],{"type":49,"value":3933},"url(.\u002Fkatex\u002Ffonts\u002F",{"type":49,"value":3935},"), and paste the full CSS as ",{"type":43,"tag":58,"props":3937,"children":3939},{"className":3938},[],[3940],{"type":49,"value":3941},"\u003Cstyle id=\"katex-inline-css\">",{"type":49,"value":3943}," in every composition and in ",{"type":43,"tag":58,"props":3945,"children":3947},{"className":3946},[],[3948],{"type":49,"value":964},{"type":49,"value":3950},". Load ",{"type":43,"tag":58,"props":3952,"children":3954},{"className":3953},[],[3955],{"type":49,"value":3956},"katex.min.js",{"type":49,"value":3958}," from the local ",{"type":43,"tag":58,"props":3960,"children":3962},{"className":3961},[],[3963],{"type":49,"value":3918},{"type":49,"value":3965}," copy, ",{"type":43,"tag":79,"props":3967,"children":3968},{},[3969],{"type":49,"value":3970},"not",{"type":49,"value":3972}," a CDN. See step-5 for the exact procedure.",{"type":43,"tag":1565,"props":3974,"children":3975},{},[3976,3981,3983,3988,3990,3996,3998,4004,4006,4011,4013,4018,4020,4025,4027,4033,4035,4040,4042,4047,4049,4054,4056,4061,4063,4068,4070,4075,4077,4082,4084,4089,4091,4097,4099,4104,4106,4111,4113,4118,4120,4125,4127,4132,4134,4140,4142,4147,4149,4155,4157,4162,4164,4169,4171,4176,4178,4183,4185,4190,4192,4197,4199,4205,4207,4213,4215,4221,4223,4228,4230,4236,4238,4242,4244,4248],{"type":43,"tag":79,"props":3977,"children":3978},{},[3979],{"type":49,"value":3980},"Circuit schematic physics accuracy (电路原理图物理正确性).",{"type":49,"value":3982}," When drawing circuit schematic diagrams (电路图) in SVG: (a) battery symbol — ",{"type":43,"tag":79,"props":3984,"children":3985},{},[3986],{"type":49,"value":3987},"长线 = 正极(+), 短线 = 负极(-)",{"type":49,"value":3989},", never reverse the labels; (b) current direction — conventional current flows from battery ",{"type":43,"tag":58,"props":3991,"children":3993},{"className":3992},[],[3994],{"type":49,"value":3995},"+",{"type":49,"value":3997}," terminal through external circuit to ",{"type":43,"tag":58,"props":3999,"children":4001},{"className":4000},[],[4002],{"type":49,"value":4003},"-",{"type":49,"value":4005}," terminal, all arrows must form a consistent closed loop; (c) ammeter — must be wired in ",{"type":43,"tag":79,"props":4007,"children":4008},{},[4009],{"type":49,"value":4010},"series",{"type":49,"value":4012},", current enters ",{"type":43,"tag":58,"props":4014,"children":4016},{"className":4015},[],[4017],{"type":49,"value":3995},{"type":49,"value":4019}," terminal; (d) voltmeter — must be wired in ",{"type":43,"tag":79,"props":4021,"children":4022},{},[4023],{"type":49,"value":4024},"parallel",{"type":49,"value":4026}," with dashed branch wires (",{"type":43,"tag":58,"props":4028,"children":4030},{"className":4029},[],[4031],{"type":49,"value":4032},"stroke-dasharray",{"type":49,"value":4034},"), ",{"type":43,"tag":58,"props":4036,"children":4038},{"className":4037},[],[4039],{"type":49,"value":3995},{"type":49,"value":4041}," terminal toward higher potential; (e) ",{"type":43,"tag":79,"props":4043,"children":4044},{},[4045],{"type":49,"value":4046},"wire segmentation (导线分段)",{"type":49,"value":4048}," — wires must STOP at component terminals, never draw a continuous wire that passes through components (components are NOT decorative overlays on wires); (f) ",{"type":43,"tag":79,"props":4050,"children":4051},{},[4052],{"type":49,"value":4053},"switch must break the circuit (开关必须断路)",{"type":49,"value":4055}," — incoming wire ends at switch pivot, outgoing wire starts at contact terminal, switch orientation must match wire direction (vertical wire → vertical switch), no zero-length line segments; (g) ",{"type":43,"tag":79,"props":4057,"children":4058},{},[4059],{"type":49,"value":4060},"layout distribution (布局分布)",{"type":49,"value":4062}," — distribute series components across multiple sides of the rectangular loop, do NOT stack all components on one side; ",{"type":43,"tag":79,"props":4064,"children":4065},{},[4066],{"type":49,"value":4067},"an empty side of the loop may be JUST a wire (导线) — never add\u002Fduplicate a component only to \"fill\" or \"balance\" a side",{"type":49,"value":4069},"; (h) ",{"type":43,"tag":79,"props":4071,"children":4072},{},[4073],{"type":49,"value":4074},"no duplicate wires",{"type":49,"value":4076}," — each wire gap between components is drawn exactly once; (h2) ",{"type":43,"tag":79,"props":4078,"children":4079},{},[4080],{"type":49,"value":4081},"component inventory correctness (元件清单正确性) — 每个真实元件恰好出现一次",{"type":49,"value":4083}," — the set of components must match the problem's actual circuit; single-instance instruments (",{"type":43,"tag":79,"props":4085,"children":4086},{},[4087],{"type":49,"value":4088},"变阻器\u002F滑动变阻器, 电流表, 电压表, 开关, 电源",{"type":49,"value":4090},") must each appear EXACTLY ONCE — never invent or duplicate a component (drawing two 变阻器 in series is a physics error). Only true multiples in the problem (e.g. 两个灯泡 L₁\u002FL₂, 两个电阻 R₁\u002FR₂) may repeat. ",{"type":43,"tag":58,"props":4092,"children":4094},{"className":4093},[],[4095],{"type":49,"value":4096},"check_circuit_inventory.py",{"type":49,"value":4098}," gates this; (h3) ",{"type":43,"tag":79,"props":4100,"children":4101},{},[4102],{"type":49,"value":4103},"circuit loop MUST be closed \u002F 电源两端必须都接线 (回路闭合)",{"type":49,"value":4105}," — the main series loop is a single closed path: trace it from the 电源 ",{"type":43,"tag":58,"props":4107,"children":4109},{"className":4108},[],[4110],{"type":49,"value":3995},{"type":49,"value":4112}," terminal through every component back to the 电源 ",{"type":43,"tag":58,"props":4114,"children":4116},{"className":4115},[],[4117],{"type":49,"value":4003},{"type":49,"value":4119}," terminal — ",{"type":43,"tag":79,"props":4121,"children":4122},{},[4123],{"type":49,"value":4124},"every gap must have a wire, and the power source's BOTH terminals must each connect to a wire",{"type":49,"value":4126},". No unwired component terminal, no ",{"type":43,"tag":79,"props":4128,"children":4129},{},[4130],{"type":49,"value":4131},"dangling wire stub",{"type":49,"value":4133}," (悬空导线端点 — a wire end that lands in empty space instead of on a component terminal or another wire at a corner). In physical wiring diagrams whose wire coordinates live in a JS array (",{"type":43,"tag":58,"props":4135,"children":4137},{"className":4136},[],[4138],{"type":49,"value":4139},"var wires=[[\"w1\",x1,y1,x2,y2],…]",{"type":49,"value":4141},"), it is easy to forget the segment that closes the left side back up to the source — ",{"type":43,"tag":79,"props":4143,"children":4144},{},[4145],{"type":49,"value":4146},"do not",{"type":49,"value":4148},". ",{"type":43,"tag":58,"props":4150,"children":4152},{"className":4151},[],[4153],{"type":49,"value":4154},"check_circuit_closed.py",{"type":49,"value":4156}," gates this; (i) ",{"type":43,"tag":79,"props":4158,"children":4159},{},[4160],{"type":49,"value":4161},"physical wiring diagram layout (实物连接图布局)",{"type":49,"value":4163}," — \"连接电表\" and \"连接主回路\" operation scenes must use a ",{"type":43,"tag":79,"props":4165,"children":4166},{},[4167],{"type":49,"value":4168},"rectangular loop",{"type":49,"value":4170}," layout (NOT a flat horizontal line) with ammeter IN the series loop (on the bottom return wire) and voltmeter on a visually separate dashed parallel branch; wire routing stays compact (≤100px margin beyond components); use Circuit Wiring Operation (C12) template from math-components.md, NOT Chemistry Operation Flow (C9); see circuit-schematic-guide.md Section 12; (j) ",{"type":43,"tag":79,"props":4172,"children":4173},{},[4174],{"type":49,"value":4175},"voltmeter connection wires must not protrude (电压表连接导线禁止突出)",{"type":49,"value":4177}," — the ",{"type":43,"tag":58,"props":4179,"children":4181},{"className":4180},[],[4182],{"type":49,"value":1443},{"type":49,"value":4184}," template has NO built-in wire stubs; terminal endpoints are at the circle edge (±30 from center, not ±50); all dashed connection wires must be drawn as separate ",{"type":43,"tag":58,"props":4186,"children":4188},{"className":4187},[],[4189],{"type":49,"value":1329},{"type":49,"value":4191}," elements from the main circuit T-junction point to the voltmeter circle edge — wires must start at the junction and end at the circle edge, never extending past either point; (k) ",{"type":43,"tag":79,"props":4193,"children":4194},{},[4195],{"type":49,"value":4196},"voltmeter branch junctions must have dots and clean routing (电压表分支点必须有圆点且路由干净)",{"type":49,"value":4198}," — every T-junction where a voltmeter dashed wire meets the main circuit wire must have a filled junction dot (",{"type":43,"tag":58,"props":4200,"children":4202},{"className":4201},[],[4203],{"type":49,"value":4204},"\u003Ccircle r=\"4-5\">",{"type":49,"value":4206},"); the dashed wire endpoint coordinates must be exactly ON the main wire (no offset); position the voltmeter so its terminals align with junction points to allow straight dashed lines; if L-shaped routing is unavoidable, use a single ",{"type":43,"tag":58,"props":4208,"children":4210},{"className":4209},[],[4211],{"type":49,"value":4212},"\u003Cpath>",{"type":49,"value":4214}," with ",{"type":43,"tag":58,"props":4216,"children":4218},{"className":4217},[],[4219],{"type":49,"value":4220},"stroke-linejoin=\"round\"",{"type":49,"value":4222}," — NEVER two separate ",{"type":43,"tag":58,"props":4224,"children":4226},{"className":4225},[],[4227],{"type":49,"value":1329},{"type":49,"value":4229}," elements (they create gaps\u002Fprotrusions at corners). Use ",{"type":43,"tag":58,"props":4231,"children":4233},{"className":4232},[],[4234],{"type":49,"value":4235},"sch-*",{"type":49,"value":4237}," templates from ",{"type":43,"tag":765,"props":4239,"children":4240},{"href":1190},[4241],{"type":49,"value":1190},{"type":49,"value":4243}," and read ",{"type":43,"tag":765,"props":4245,"children":4246},{"href":1288},[4247],{"type":49,"value":1288},{"type":49,"value":4249}," for the full pre-flight checklist.",{"type":43,"tag":1565,"props":4251,"children":4252},{},[4253,4258,4260,4266,4268,4273,4275,4280,4281,4287,4289,4294,4296,4302,4304,4309,4311,4317,4319,4324,4326,4332,4334,4340,4342,4348,4349,4353,4354,4360,4361,4367],{"type":43,"tag":79,"props":4254,"children":4255},{},[4256],{"type":49,"value":4257},"Caption formulas use KaTeX inline rendering (字幕公式必须用KaTeX渲染).",{"type":49,"value":4259}," Video captions must display proper math notation — never show spoken-form Chinese like \"G M m除以R的平方\". When assembling caption ",{"type":43,"tag":58,"props":4261,"children":4263},{"className":4262},[],[4264],{"type":49,"value":4265},"\u003Cdiv>",{"type":49,"value":4267}," elements in ",{"type":43,"tag":58,"props":4269,"children":4271},{"className":4270},[],[4272],{"type":49,"value":964},{"type":49,"value":4274},", replace all spoken-form math expressions from ",{"type":43,"tag":58,"props":4276,"children":4278},{"className":4277},[],[4279],{"type":49,"value":869},{"type":49,"value":4214},{"type":43,"tag":58,"props":4282,"children":4284},{"className":4283},[],[4285],{"type":49,"value":4286},"\u003Cspan class=\"cm\">LaTeX code\u003C\u002Fspan>",{"type":49,"value":4288}," elements. The root ",{"type":43,"tag":58,"props":4290,"children":4292},{"className":4291},[],[4293],{"type":49,"value":964},{"type":49,"value":4295}," must load KaTeX JS and include a script that renders all ",{"type":43,"tag":58,"props":4297,"children":4299},{"className":4298},[],[4300],{"type":49,"value":4301},".cm",{"type":49,"value":4303}," spans with ",{"type":43,"tag":58,"props":4305,"children":4307},{"className":4306},[],[4308],{"type":49,"value":1721},{"type":49,"value":4310}," in inline mode. Use ",{"type":43,"tag":58,"props":4312,"children":4314},{"className":4313},[],[4315],{"type":49,"value":4316},"\\tfrac",{"type":49,"value":4318}," for fractions (compact, fits single-line captions). ",{"type":43,"tag":79,"props":4320,"children":4321},{},[4322],{"type":49,"value":4323},"KaTeX angle pitfall:",{"type":49,"value":4325}," for degrees use ",{"type":43,"tag":58,"props":4327,"children":4329},{"className":4328},[],[4330],{"type":49,"value":4331},"^\\circ",{"type":49,"value":4333},", for arcminutes use a plain ASCII ",{"type":43,"tag":58,"props":4335,"children":4337},{"className":4336},[],[4338],{"type":49,"value":4339},"'",{"type":49,"value":4341}," (prime), for arcseconds use ",{"type":43,"tag":58,"props":4343,"children":4345},{"className":4344},[],[4346],{"type":49,"value":4347},"''",{"type":49,"value":4148},{"type":43,"tag":79,"props":4350,"children":4351},{},[4352],{"type":49,"value":3167},{"type":49,"value":3169},{"type":43,"tag":58,"props":4355,"children":4357},{"className":4356},[],[4358],{"type":49,"value":4359},"\\'",{"type":49,"value":1723},{"type":43,"tag":58,"props":4362,"children":4364},{"className":4363},[],[4365],{"type":49,"value":4366},"\\\"",{"type":49,"value":4368}," in KaTeX math — these are text-mode accent commands that cause the entire formula to render as red error text. See step-6 for the full template, common replacements table, KaTeX pitfalls, and examples.",{"type":43,"tag":1565,"props":4370,"children":4371},{},[4372,4377,4379,4385,4387,4393,4395,4400,4402,4407,4408,4414,4416,4422,4424,4435,4437,4442,4444,4450,4452,4458,4460,4465,4467,4472,4474,4480,4482,4487,4489,4495,4497,4503,4505,4510],{"type":43,"tag":79,"props":4373,"children":4374},{},[4375],{"type":49,"value":4376},"Self-hosted offline fonts — Chinese MUST NOT be tofu (中文字体必须离线内嵌).",{"type":49,"value":4378}," The font files are shipped with this skill under ",{"type":43,"tag":58,"props":4380,"children":4382},{"className":4381},[],[4383],{"type":49,"value":4384},"assets\u002Ffonts\u002F",{"type":49,"value":4386}," and MUST be copied into ",{"type":43,"tag":58,"props":4388,"children":4390},{"className":4389},[],[4391],{"type":49,"value":4392},"dist\u002Fassets\u002Ffonts\u002F",{"type":49,"value":4394}," (see step-5 Prerequisites). Every sub-composition file AND the root ",{"type":43,"tag":58,"props":4396,"children":4398},{"className":4397},[],[4399],{"type":49,"value":964},{"type":49,"value":4401}," MUST embed them via an inline ",{"type":43,"tag":58,"props":4403,"children":4405},{"className":4404},[],[4406],{"type":49,"value":3888},{"type":49,"value":343},{"type":43,"tag":58,"props":4409,"children":4411},{"className":4410},[],[4412],{"type":49,"value":4413},"\u003Cstyle>",{"type":49,"value":4415}," block (the exact block is in step-6 for the root and ",{"type":43,"tag":58,"props":4417,"children":4419},{"className":4418},[],[4420],{"type":49,"value":4421},"assets\u002Fk12-scholar-font-template.css",{"type":49,"value":4423}," for compositions). ",{"type":43,"tag":79,"props":4425,"children":4426},{},[4427,4429],{"type":49,"value":4428},"Do NOT use a Google Fonts \u002F CDN ",{"type":43,"tag":58,"props":4430,"children":4432},{"className":4431},[],[4433],{"type":49,"value":4434},"\u003Clink>",{"type":49,"value":4436}," and do NOT rely on the compiler's built-in embedding or system fonts (PingFang SC): the render sandbox is air-gapped, so a CDN font that fails to load makes ALL Chinese render as garbled boxes (乱码). Use a ",{"type":43,"tag":79,"props":4438,"children":4439},{},[4440],{"type":49,"value":4441},"CJK-first",{"type":49,"value":4443}," stack everywhere — ",{"type":43,"tag":58,"props":4445,"children":4447},{"className":4446},[],[4448],{"type":49,"value":4449},"font-family: \"Noto Sans SC\", Inter, sans-serif",{"type":49,"value":4451}," — and never ",{"type":43,"tag":58,"props":4453,"children":4455},{"className":4454},[],[4456],{"type":49,"value":4457},"Inter, sans-serif",{"type":49,"value":4459}," alone. ",{"type":43,"tag":79,"props":4461,"children":4462},{},[4463],{"type":49,"value":4464},"This applies no matter HOW font-family is set",{"type":49,"value":4466}," — SVG ",{"type":43,"tag":58,"props":4468,"children":4470},{"className":4469},[],[4471],{"type":49,"value":1747},{"type":49,"value":4473}," attr, inline ",{"type":43,"tag":58,"props":4475,"children":4477},{"className":4476},[],[4478],{"type":49,"value":4479},"style=",{"type":49,"value":4481},", AND CSS ",{"type":43,"tag":79,"props":4483,"children":4484},{},[4485],{"type":49,"value":4486},"class rules",{"type":49,"value":4488},". The #1 miss is a small label\u002Feyebrow\u002Fbadge class (e.g. ",{"type":43,"tag":58,"props":4490,"children":4492},{"className":4491},[],[4493],{"type":49,"value":4494},".block-number{font-family:Inter,sans-serif}",{"type":49,"value":4496},") that the design intends for a Latin number like \"01\" but the model fills with Chinese (对称性\u002F题目所给) → those 3–4 characters render as 豆腐块 while the rest of the card is fine. Any class that could ever hold Chinese must include ",{"type":43,"tag":58,"props":4498,"children":4500},{"className":4499},[],[4501],{"type":49,"value":4502},"\"Noto Sans SC\"",{"type":49,"value":4504},". Enforced by ",{"type":43,"tag":58,"props":4506,"children":4508},{"className":4507},[],[4509],{"type":49,"value":1820},{"type":49,"value":4511}," (now also scans class-based font-family, not just SVG\u002Finline).",{"type":43,"tag":1565,"props":4513,"children":4514},{},[4515,4520,4522,4528,4529,4535,4537,4543,4545,4550,4552,4557,4559,4564,4566,4571,4573,4579,4581,4587,4589,4595,4597,4603,4605,4610,4612,4618,4620,4625,4627,4633,4634,4638,4639,4644,4645,4651,4652,4658,4660,4665,4666,4671,4673,4706,4708,4714,4715,4720],{"type":43,"tag":79,"props":4516,"children":4517},{},[4518],{"type":49,"value":4519},"No CDN\u002Fexternal URLs at render time (渲染时禁止任何CDN\u002F外部URL).",{"type":49,"value":4521}," Every ",{"type":43,"tag":58,"props":4523,"children":4525},{"className":4524},[],[4526],{"type":49,"value":4527},"\u003Cscript src>",{"type":49,"value":455},{"type":43,"tag":58,"props":4530,"children":4532},{"className":4531},[],[4533],{"type":49,"value":4534},"\u003Clink href>",{"type":49,"value":4536},", and CSS ",{"type":43,"tag":58,"props":4538,"children":4540},{"className":4539},[],[4541],{"type":49,"value":4542},"url()",{"type":49,"value":4544}," in compositions and ",{"type":43,"tag":58,"props":4546,"children":4548},{"className":4547},[],[4549],{"type":49,"value":964},{"type":49,"value":4551}," MUST use local relative paths. The AP render sandbox is ",{"type":43,"tag":79,"props":4553,"children":4554},{},[4555],{"type":49,"value":4556},"air-gapped",{"type":49,"value":4558}," with no internet access — CDN URLs that load successfully on a local machine will ",{"type":43,"tag":79,"props":4560,"children":4561},{},[4562],{"type":49,"value":4563},"silently fail",{"type":49,"value":4565}," on AP, breaking GSAP timelines (no animations), KaTeX rendering (raw LaTeX in captions), and font loading (tofu boxes). Specifically: (a) GSAP — load from ",{"type":43,"tag":58,"props":4567,"children":4569},{"className":4568},[],[4570],{"type":49,"value":2632},{"type":49,"value":4572}," (shipped in ",{"type":43,"tag":58,"props":4574,"children":4576},{"className":4575},[],[4577],{"type":49,"value":4578},"assets\u002Fgsap\u002F",{"type":49,"value":4580},", copied to ",{"type":43,"tag":58,"props":4582,"children":4584},{"className":4583},[],[4585],{"type":49,"value":4586},"dist\u002Fgsap\u002F",{"type":49,"value":4588}," AND ",{"type":43,"tag":58,"props":4590,"children":4592},{"className":4591},[],[4593],{"type":49,"value":4594},"dist\u002Fcompositions\u002Fgsap\u002F",{"type":49,"value":4596}," in step-5); (b) KaTeX JS — load from ",{"type":43,"tag":58,"props":4598,"children":4600},{"className":4599},[],[4601],{"type":49,"value":4602},".\u002Fkatex\u002Fkatex.min.js",{"type":49,"value":4604},"; (c) KaTeX CSS — inline as ",{"type":43,"tag":58,"props":4606,"children":4608},{"className":4607},[],[4609],{"type":49,"value":4413},{"type":49,"value":4611},", fonts from ",{"type":43,"tag":58,"props":4613,"children":4615},{"className":4614},[],[4616],{"type":49,"value":4617},".\u002Fkatex\u002Ffonts\u002F",{"type":49,"value":4619},"; (d) body fonts — inline ",{"type":43,"tag":58,"props":4621,"children":4623},{"className":4622},[],[4624],{"type":49,"value":3888},{"type":49,"value":4626}," from ",{"type":43,"tag":58,"props":4628,"children":4630},{"className":4629},[],[4631],{"type":49,"value":4632},".\u002Fassets\u002Ffonts\u002F",{"type":49,"value":4148},{"type":43,"tag":79,"props":4635,"children":4636},{},[4637],{"type":49,"value":3895},{"type":49,"value":3169},{"type":43,"tag":58,"props":4640,"children":4642},{"className":4641},[],[4643],{"type":49,"value":1687},{"type":49,"value":455},{"type":43,"tag":58,"props":4646,"children":4648},{"className":4647},[],[4649],{"type":49,"value":4650},"cdnjs.cloudflare.com",{"type":49,"value":455},{"type":43,"tag":58,"props":4653,"children":4655},{"className":4654},[],[4656],{"type":49,"value":4657},"unpkg.com",{"type":49,"value":4659},", or any other external host in a ",{"type":43,"tag":58,"props":4661,"children":4663},{"className":4662},[],[4664],{"type":49,"value":1695},{"type":49,"value":1723},{"type":43,"tag":58,"props":4667,"children":4669},{"className":4668},[],[4670],{"type":49,"value":1703},{"type":49,"value":4672}," attribute. ",{"type":43,"tag":79,"props":4674,"children":4675},{},[4676,4678,4683,4685,4690,4692,4697,4699,4704],{"type":49,"value":4677},"CRITICAL: sub-compositions in ",{"type":43,"tag":58,"props":4679,"children":4681},{"className":4680},[],[4682],{"type":49,"value":2381},{"type":49,"value":4684}," resolve ",{"type":43,"tag":58,"props":4686,"children":4688},{"className":4687},[],[4689],{"type":49,"value":1651},{"type":49,"value":4691}," relative to their own directory — the assets MUST be mirrored into ",{"type":43,"tag":58,"props":4693,"children":4695},{"className":4694},[],[4696],{"type":49,"value":2381},{"type":49,"value":4698}," (gsap\u002F, katex\u002F, assets\u002F) or all ",{"type":43,"tag":58,"props":4700,"children":4702},{"className":4701},[],[4703],{"type":49,"value":1651},{"type":49,"value":4705}," paths silently 404 and GSAP\u002FKaTeX\u002Ffonts fail to load (blank scenes, no animations, tofu).",{"type":49,"value":4707}," Enforced by ",{"type":43,"tag":58,"props":4709,"children":4711},{"className":4710},[],[4712],{"type":49,"value":4713},"scripts\u002Fcheck_asset_mirror.py",{"type":49,"value":470},{"type":43,"tag":58,"props":4716,"children":4718},{"className":4717},[],[4719],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":4722,"children":4723},{},[4724,4729,4731,4737,4739,4744,4746,4751,4753,4757,4759,4764,4765,4771,4773,4779,4781,4787,4788,4794,4796,4801,4803,4809,4811,4816,4818,4823,4824,4829,4831,4837,4839,4844],{"type":43,"tag":79,"props":4725,"children":4726},{},[4727],{"type":49,"value":4728},"Defensive rendering — no CSS-hidden content (防御性渲染 — CSS禁止隐藏内容).",{"type":49,"value":4730}," The #1 cause of blank videos is CSS ",{"type":43,"tag":58,"props":4732,"children":4734},{"className":4733},[],[4735],{"type":49,"value":4736},"opacity: 0",{"type":49,"value":4738}," on ",{"type":43,"tag":58,"props":4740,"children":4742},{"className":4741},[],[4743],{"type":49,"value":2009},{"type":49,"value":4745}," or content elements, combined with a JS error that prevents GSAP from revealing them. ",{"type":43,"tag":79,"props":4747,"children":4748},{},[4749],{"type":49,"value":4750},"Rules:",{"type":49,"value":4752}," (a) ",{"type":43,"tag":79,"props":4754,"children":4755},{},[4756],{"type":49,"value":3895},{"type":49,"value":4758}," set ",{"type":43,"tag":58,"props":4760,"children":4762},{"className":4761},[],[4763],{"type":49,"value":4736},{"type":49,"value":455},{"type":43,"tag":58,"props":4766,"children":4768},{"className":4767},[],[4769],{"type":49,"value":4770},"visibility: hidden",{"type":49,"value":4772},", or ",{"type":43,"tag":58,"props":4774,"children":4776},{"className":4775},[],[4777],{"type":49,"value":4778},"display: none",{"type":49,"value":4780}," on any content element in CSS — GSAP handles the initial hidden state dynamically via ",{"type":43,"tag":58,"props":4782,"children":4784},{"className":4783},[],[4785],{"type":49,"value":4786},"autoAlpha: 0",{"type":49,"value":470},{"type":43,"tag":58,"props":4789,"children":4791},{"className":4790},[],[4792],{"type":49,"value":4793},"fromTo()",{"type":49,"value":4795},"; (b) Wrap the ",{"type":43,"tag":79,"props":4797,"children":4798},{},[4799],{"type":49,"value":4800},"entire",{"type":49,"value":4802}," GSAP timeline construction in a ",{"type":43,"tag":58,"props":4804,"children":4806},{"className":4805},[],[4807],{"type":49,"value":4808},"try-catch",{"type":49,"value":4810}," block — if any tween fails, the timeline still registers and content stays visible; (c) Each ",{"type":43,"tag":58,"props":4812,"children":4814},{"className":4813},[],[4815],{"type":49,"value":1721},{"type":49,"value":4817}," call must have its ",{"type":43,"tag":79,"props":4819,"children":4820},{},[4821],{"type":49,"value":4822},"own",{"type":49,"value":343},{"type":43,"tag":58,"props":4825,"children":4827},{"className":4826},[],[4828],{"type":49,"value":4808},{"type":49,"value":4830}," — one bad LaTeX string must not crash the entire composition; (d) Always pass ",{"type":43,"tag":58,"props":4832,"children":4834},{"className":4833},[],[4835],{"type":49,"value":4836},"throwOnError: false",{"type":49,"value":4838}," to ",{"type":43,"tag":58,"props":4840,"children":4842},{"className":4841},[],[4843],{"type":49,"value":1721},{"type":49,"value":4845},". See step-5 \"Defensive Script Pattern\" for the complete code template.",{"type":43,"tag":1565,"props":4847,"children":4848},{},[4849,4854,4856,4860,4861,4866,4868,4873,4875,4880,4882,4887,4889,4894,4896,4901,4903,4908,4910,4915,4917,4922,4924,4929,4930,4935,4936,4941,4943,4949,4950,4956,4957,4963,4964,4970,4972,4977],{"type":43,"tag":79,"props":4850,"children":4851},{},[4852],{"type":49,"value":4853},"Reproducibility — stable structure every run (稳定复现 — 结构固定).",{"type":49,"value":4855}," The same problem must yield the same well-formed video every time; the biggest source of run-to-run quality swing is the BUILD step deviating from the plan. ",{"type":43,"tag":79,"props":4857,"children":4858},{},[4859],{"type":49,"value":4750},{"type":49,"value":4752},{"type":43,"tag":79,"props":4862,"children":4863},{},[4864],{"type":49,"value":4865},"Fixed output path",{"type":49,"value":4867}," — scaffold and render in exactly ",{"type":43,"tag":58,"props":4869,"children":4871},{"className":4870},[],[4872],{"type":49,"value":1394},{"type":49,"value":4874}," at the workspace root; never nest it (",{"type":43,"tag":58,"props":4876,"children":4878},{"className":4877},[],[4879],{"type":49,"value":1386},{"type":49,"value":4881}," etc.) — the renderer and gates assume ",{"type":43,"tag":58,"props":4883,"children":4885},{"className":4884},[],[4886],{"type":49,"value":1394},{"type":49,"value":4888},". (b) ",{"type":43,"tag":79,"props":4890,"children":4891},{},[4892],{"type":49,"value":4893},"One composition per storyboard scene",{"type":49,"value":4895}," — build exactly the scenes listed in ",{"type":43,"tag":58,"props":4897,"children":4899},{"className":4898},[],[4900],{"type":49,"value":902},{"type":49,"value":4902},", one concept\u002Fray\u002Fstep per scene; never merge several planned scenes into one file, never skip a planned scene. ",{"type":43,"tag":58,"props":4904,"children":4906},{"className":4905},[],[4907],{"type":49,"value":964},{"type":49,"value":4909}," must wire ≥ the planned scene count. (c) ",{"type":43,"tag":79,"props":4911,"children":4912},{},[4913],{"type":49,"value":4914},"Every scene self-contained & complete",{"type":49,"value":4916}," — each composition draws its full content (e.g. an optics scene MUST include the horizontal principal axis through O with 2F\u002FF\u002FO\u002FF′\u002F2F′ marked) and registers ",{"type":43,"tag":58,"props":4918,"children":4920},{"className":4919},[],[4921],{"type":49,"value":2292},{"type":49,"value":4923},". (d) ",{"type":43,"tag":79,"props":4925,"children":4926},{},[4927],{"type":49,"value":4928},"Determinism",{"type":49,"value":1681},{"type":43,"tag":58,"props":4931,"children":4933},{"className":4932},[],[4934],{"type":49,"value":3230},{"type":49,"value":190},{"type":43,"tag":58,"props":4937,"children":4939},{"className":4938},[],[4940],{"type":49,"value":3237},{"type":49,"value":4942}," \u002F async timeline construction (see rule #7). (e) These are enforced deterministically by ",{"type":43,"tag":58,"props":4944,"children":4946},{"className":4945},[],[4947],{"type":49,"value":4948},"$EDU_SKILL_ROOT\u002Fscripts\u002Fcheck_scene_coverage.py",{"type":49,"value":1139},{"type":43,"tag":58,"props":4951,"children":4953},{"className":4952},[],[4954],{"type":49,"value":4955},"$EDU_SKILL_ROOT\u002Fscripts\u002Fcheck_no_hidden_content.py",{"type":49,"value":2804},{"type":43,"tag":58,"props":4958,"children":4960},{"className":4959},[],[4961],{"type":49,"value":4962},"$EDU_SKILL_ROOT\u002Fscripts\u002Fprecheck.py",{"type":49,"value":2003},{"type":43,"tag":58,"props":4965,"children":4967},{"className":4966},[],[4968],{"type":49,"value":4969},"precheck.py dist",{"type":49,"value":4971}," must print ",{"type":43,"tag":58,"props":4973,"children":4975},{"className":4974},[],[4976],{"type":49,"value":1547},{"type":49,"value":4978}," before rendering.",{"type":43,"tag":1565,"props":4980,"children":4981},{},[4982,4994,4996,5001,5003,5012,5014,5019,5020,5025,5027,5032,5034,5039,5041,5047,5049,5055,5057,5063,5065,5071,5072,5077],{"type":43,"tag":79,"props":4983,"children":4984},{},[4985,4987,4992],{"type":49,"value":4986},"Root MUST reference scenes via ",{"type":43,"tag":58,"props":4988,"children":4990},{"className":4989},[],[4991],{"type":49,"value":2285},{"type":49,"value":4993}," (根合成引用名固定 — 否则全白).",{"type":49,"value":4995}," In ",{"type":43,"tag":58,"props":4997,"children":4999},{"className":4998},[],[5000],{"type":49,"value":2458},{"type":49,"value":5002},", every scene clip MUST use the EXACT attribute ",{"type":43,"tag":79,"props":5004,"children":5005},{},[5006],{"type":43,"tag":58,"props":5007,"children":5009},{"className":5008},[],[5010],{"type":49,"value":5011},"data-composition-src=\"compositions\u002Fscene-X.html\"",{"type":49,"value":5013}," — never ",{"type":43,"tag":58,"props":5015,"children":5017},{"className":5016},[],[5018],{"type":49,"value":2321},{"type":49,"value":455},{"type":43,"tag":58,"props":5021,"children":5023},{"className":5022},[],[5024],{"type":49,"value":1695},{"type":49,"value":5026},", or a missing src. HyperFrames only loads\u002Fcompiles an external scene when it sees ",{"type":43,"tag":58,"props":5028,"children":5030},{"className":5029},[],[5031],{"type":49,"value":2285},{"type":49,"value":5033},"; any other name means ",{"type":43,"tag":79,"props":5035,"children":5036},{},[5037],{"type":49,"value":5038},"no scene loads and the whole video renders white",{"type":49,"value":5040}," (only the caption bar shows). Also: each clip's ",{"type":43,"tag":58,"props":5042,"children":5044},{"className":5043},[],[5045],{"type":49,"value":5046},"data-composition-id",{"type":49,"value":5048}," must match that scene's registered ",{"type":43,"tag":58,"props":5050,"children":5052},{"className":5051},[],[5053],{"type":49,"value":5054},"window.__timelines[\"\u003Cid>\"]",{"type":49,"value":5056},", and scenes are laid out directly inside the root composition div (do NOT wrap them in an extra ",{"type":43,"tag":58,"props":5058,"children":5060},{"className":5059},[],[5061],{"type":49,"value":5062},"data-composition-id=\"mt-root\"",{"type":49,"value":5064}," container). Enforced by ",{"type":43,"tag":58,"props":5066,"children":5068},{"className":5067},[],[5069],{"type":49,"value":5070},"scripts\u002Fcheck_root_compositions.py",{"type":49,"value":470},{"type":43,"tag":58,"props":5073,"children":5075},{"className":5074},[],[5076],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":5079,"children":5080},{},[5081,5086,5088,5093,5095,5100,5102,5107,5109,5114,5116,5122,5124,5129,5131,5137,5139,5144],{"type":43,"tag":79,"props":5082,"children":5083},{},[5084],{"type":49,"value":5085},"几何图形坐标必须数学精确 (Geometry SVG coordinates must be mathematically exact).",{"type":49,"value":5087}," When drawing geometric figures in SVG (Geometry Canvas scenes), all vertex coordinates MUST be computed from the problem's mathematical constraints (perpendicularity, parallelism, similarity ratios, length ratios, etc.) — ",{"type":43,"tag":79,"props":5089,"children":5090},{},[5091],{"type":49,"value":5092},"never placed by visual estimation",{"type":49,"value":5094},". Mandatory steps: (a) solve the problem's equations to get exact proportional relationships between all segments before writing any SVG; (b) choose a base unit in the SVG viewBox, then compute every point's coordinates from the ratios; (c) ",{"type":43,"tag":79,"props":5096,"children":5097},{},[5098],{"type":49,"value":5099},"verify perpendicularity",{"type":49,"value":5101}," — if two lines are perpendicular, compute the dot product of their direction vectors and confirm it equals 0; (d) ",{"type":43,"tag":79,"props":5103,"children":5104},{},[5105],{"type":49,"value":5106},"verify length ratios",{"type":49,"value":5108}," — compute Euclidean distances and confirm they match the problem's given ratios; (e) write a ",{"type":43,"tag":58,"props":5110,"children":5112},{"className":5111},[],[5113],{"type":49,"value":2345},{"type":49,"value":5115}," block above the SVG with a ",{"type":43,"tag":58,"props":5117,"children":5119},{"className":5118},[],[5120],{"type":49,"value":5121},"POINTS:",{"type":49,"value":5123}," line declaring all coordinates and ",{"type":43,"tag":58,"props":5125,"children":5127},{"className":5126},[],[5128],{"type":49,"value":2353},{"type":49,"value":5130}," lines for every problem constraint (parallel, perpendicular, midpoint, on_line, on_segment, intersection, ratio, collinear) — the pre-render gate ",{"type":43,"tag":58,"props":5132,"children":5134},{"className":5133},[],[5135],{"type":49,"value":5136},"scripts\u002Fcheck_geometry_verification.py dist",{"type":49,"value":5138}," mathematically verifies every assertion against the declared coordinates and exits 0; (f) ",{"type":43,"tag":79,"props":5140,"children":5141},{},[5142],{"type":49,"value":5143},"right angle marks (直角符号)",{"type":49,"value":5145}," must be drawn as small L-shaped squares (not V-shapes or arcs), with edges computed from the actual line direction unit vectors — see step-5-build-components.md \"Geometry Coordinate Accuracy\" for the verification block format, assertion types, and construction algorithms.",{"type":43,"tag":1565,"props":5147,"children":5148},{},[5149,5154,5156,5161,5163,5173,5178,5187,5192,5194,5199],{"type":43,"tag":79,"props":5150,"children":5151},{},[5152],{"type":49,"value":5153},"角度弧线必须用叉积确定扫描方向 (Angle arcs must use cross-product to determine sweep direction).",{"type":49,"value":5155}," When marking a non-right angle at vertex V with arms pointing to adjacent vertices P1 and P2 in SVG, the arc MUST sweep through the ",{"type":43,"tag":79,"props":5157,"children":5158},{},[5159],{"type":49,"value":5160},"interior",{"type":49,"value":5162}," of the angle being marked. The sweep direction is determined by the cross product of the two arm vectors — never by visual estimation or guesswork. Algorithm:\n",{"type":43,"tag":571,"props":5164,"children":5168},{"className":5165,"code":5167,"language":49},[5166],"language-text","\u002F\u002F 1. Direction vectors from vertex V to each arm endpoint\ndx1 = P1.x - V.x;  dy1 = P1.y - V.y;\ndx2 = P2.x - V.x;  dy2 = P2.y - V.y;\n\n\u002F\u002F 2. Cross product (in SVG screen coordinates, y-down)\ncross = dx1 * dy2 - dy1 * dx2;\n\n\u002F\u002F 3. Arc start\u002Fend points on each arm at radius distance\nangle1 = Math.atan2(dy1, dx1);\nangle2 = Math.atan2(dy2, dx2);\nstartX = V.x + R * Math.cos(angle1);\nstartY = V.y + R * Math.sin(angle1);\nendX   = V.x + R * Math.cos(angle2);\nendY   = V.y + R * Math.sin(angle2);\n\n\u002F\u002F 4. Sweep flag: cross > 0 → interior is CW → sweep=1\n\u002F\u002F                cross \u003C 0 → interior is CCW → sweep=0\nsweepFlag = cross > 0 ? 1 : 0;\n\n\u002F\u002F 5. SVG path (large-arc-flag = 0 for angles \u003C 180°)\nd = `M ${startX},${startY} A ${R},${R} 0 0,${sweepFlag} ${endX},${endY}`;\n",[5169],{"type":43,"tag":58,"props":5170,"children":5171},{"__ignoreMap":576},[5172],{"type":49,"value":5167},{"type":43,"tag":79,"props":5174,"children":5175},{},[5176],{"type":49,"value":5177},"Worked example — rhombus with ∠A = 60°, ∠B = 120°:",{"type":43,"tag":571,"props":5179,"children":5182},{"className":5180,"code":5181,"language":49},[5166],"\u002F\u002F Vertices: A(200,400) B(400,180) C(600,400) D(400,620), R=30\n\n\u002F\u002F ∠A = 60° — arms AB and AD\ndx1=200, dy1=-220; dx2=200, dy2=220;\ncross = 200*220 - (-220)*200 = 88000 > 0 → sweep=1\n\u002F\u002F Arc sweeps CW through interior (rightward between AB and AD) ✅\n\n\u002F\u002F ∠B = 120° — arms BA and BC\ndx1=-200, dy1=220; dx2=200, dy2=220;\ncross = (-200)*220 - 220*200 = -88000 \u003C 0 → sweep=0\n\u002F\u002F Arc sweeps CCW through interior (downward between BA and BC) ✅\n",[5183],{"type":43,"tag":58,"props":5184,"children":5185},{"__ignoreMap":576},[5186],{"type":49,"value":5181},{"type":43,"tag":79,"props":5188,"children":5189},{},[5190],{"type":49,"value":5191},"Common mistakes:",{"type":49,"value":5193}," (a) Hardcoding sweep-flag=1 or sweep-flag=0 without computing cross product — causes arcs to appear on the exterior (reflex) side of the angle; (b) Drawing the arc between the wrong pair of arms (e.g., marking ∠ABC but using arms BA and BD); (c) Using a right-angle L-shaped mark for non-90° angles; (d) Forgetting that SVG y-axis points down, which reverses the visual rotation direction. ",{"type":43,"tag":79,"props":5195,"children":5196},{},[5197],{"type":49,"value":5198},"Always verify:",{"type":49,"value":5200}," visually, the arc must curve TOWARD the interior of the polygon, not away from it.",{"type":43,"tag":1565,"props":5202,"children":5203},{},[5204,5209,5211,5216,5218,5224,5226,5231,5233,5238,5240,5246,5248,5253,5255,5261,5263,5268,5270,5275,5277,5283,5285,5294,5296,5302,5304,5310],{"type":43,"tag":79,"props":5205,"children":5206},{},[5207],{"type":49,"value":5208},"坐标分隔符只用逗号，禁止分号 (Coordinate separators: comma only, never semicolon).",{"type":49,"value":5210}," When writing coordinate pairs, point notation, or ordered tuples in KaTeX (e.g., vertex coordinates, intersection points), use ",{"type":43,"tag":79,"props":5212,"children":5213},{},[5214],{"type":49,"value":5215},"only a comma",{"type":49,"value":5217}," as the separator between components — never a semicolon, and never both. Add a KaTeX thin space ",{"type":43,"tag":58,"props":5219,"children":5221},{"className":5220},[],[5222],{"type":49,"value":5223},"\\,",{"type":49,"value":5225}," after the comma for readability. ",{"type":43,"tag":79,"props":5227,"children":5228},{},[5229],{"type":49,"value":5230},"JS string escaping pitfall:",{"type":49,"value":5232}," in a JavaScript string passed to ",{"type":43,"tag":58,"props":5234,"children":5236},{"className":5235},[],[5237],{"type":49,"value":1721},{"type":49,"value":5239},", write ",{"type":43,"tag":58,"props":5241,"children":5243},{"className":5242},[],[5244],{"type":49,"value":5245},"\\\\,",{"type":49,"value":5247}," (double backslash) so KaTeX receives ",{"type":43,"tag":58,"props":5249,"children":5251},{"className":5250},[],[5252],{"type":49,"value":5223},{"type":49,"value":5254},". Writing ",{"type":43,"tag":58,"props":5256,"children":5258},{"className":5257},[],[5259],{"type":49,"value":5260},"\\;",{"type":49,"value":5262}," in a JS string does NOT produce a KaTeX thick space — JS treats the unknown escape ",{"type":43,"tag":58,"props":5264,"children":5266},{"className":5265},[],[5267],{"type":49,"value":5260},{"type":49,"value":5269}," as a literal semicolon character ",{"type":43,"tag":58,"props":5271,"children":5273},{"className":5272},[],[5274],{"type":49,"value":680},{"type":49,"value":5276},", so KaTeX renders a visible ",{"type":43,"tag":58,"props":5278,"children":5280},{"className":5279},[],[5281],{"type":49,"value":5282},",;",{"type":49,"value":5284}," (comma followed by semicolon). This is a confirmed rendering bug. Correct examples:\n",{"type":43,"tag":571,"props":5286,"children":5289},{"className":5287,"code":5288,"language":49},[5166],"\u002F\u002F In JS strings (double-escaped for JS → KaTeX):\nkatex.render(\"(1,\\\\, b-1)\", el);          \u002F\u002F ✅ renders (1, b−1)\nkatex.render(\"(x_0,\\\\, y_0)\", el);        \u002F\u002F ✅ renders (x₀, y₀)\n\n\u002F\u002F WRONG:\nkatex.render(\"(1,\\; b-1)\", el);           \u002F\u002F ❌ renders (1,; b−1) — JS turns \\; into literal ;\nkatex.render(\"(1,\\\\; b-1)\", el);          \u002F\u002F ❌ renders (1, b−1) with excessive space\n\n\u002F\u002F In HTML (e.g., \u003Cspan class=\"cm\"> for caption KaTeX):\n\u003Cspan class=\"cm\">(1,\\, b-1)\u003C\u002Fspan>        \u002F\u002F ✅\n\u003Cspan class=\"cm\">(1,\\; b-1)\u003C\u002Fspan>        \u002F\u002F ❌ overly wide space, inconsistent\n",[5290],{"type":43,"tag":58,"props":5291,"children":5292},{"__ignoreMap":576},[5293],{"type":49,"value":5288},{"type":49,"value":5295},"\nGeneral rule: coordinates are ",{"type":43,"tag":58,"props":5297,"children":5299},{"className":5298},[],[5300],{"type":49,"value":5301},"(a,\\, b)",{"type":49,"value":5303}," in LaTeX, ",{"type":43,"tag":58,"props":5305,"children":5307},{"className":5306},[],[5308],{"type":49,"value":5309},"(a,\\\\, b)",{"type":49,"value":5311}," in JS strings. No semicolons anywhere in coordinate notation — this is Chinese math convention (中国数学用逗号分隔坐标，不用分号).",{"type":43,"tag":1565,"props":5313,"children":5314},{},[5315,5320,5322,5328,5330,5336,5338,5343,5344,5350,5352,5356,5358,5364,5365,5371,5372,5378,5379,5385,5387,5392,5394,5400,5401,5406],{"type":43,"tag":79,"props":5316,"children":5317},{},[5318],{"type":49,"value":5319},"Sub-composition files must be HTML fragments, NOT full documents (子合成文件必须是HTML片段，不能是完整HTML文档).",{"type":49,"value":5321}," Every file in ",{"type":43,"tag":58,"props":5323,"children":5325},{"className":5324},[],[5326],{"type":49,"value":5327},"dist\u002Fcompositions\u002F*.html",{"type":49,"value":5329}," must be an HTML fragment — a root ",{"type":43,"tag":58,"props":5331,"children":5333},{"className":5332},[],[5334],{"type":49,"value":5335},"\u003Cdiv data-composition-id=\"...\">",{"type":49,"value":5337}," with inline ",{"type":43,"tag":58,"props":5339,"children":5341},{"className":5340},[],[5342],{"type":49,"value":4413},{"type":49,"value":1139},{"type":43,"tag":58,"props":5345,"children":5347},{"className":5346},[],[5348],{"type":49,"value":5349},"\u003Cscript>",{"type":49,"value":5351}," blocks. ",{"type":43,"tag":79,"props":5353,"children":5354},{},[5355],{"type":49,"value":3167},{"type":49,"value":5357}," wrap sub-compositions in ",{"type":43,"tag":58,"props":5359,"children":5361},{"className":5360},[],[5362],{"type":49,"value":5363},"\u003C!doctype html>",{"type":49,"value":455},{"type":43,"tag":58,"props":5366,"children":5368},{"className":5367},[],[5369],{"type":49,"value":5370},"\u003Chtml>",{"type":49,"value":455},{"type":43,"tag":58,"props":5373,"children":5375},{"className":5374},[],[5376],{"type":49,"value":5377},"\u003Chead>",{"type":49,"value":4772},{"type":43,"tag":58,"props":5380,"children":5382},{"className":5381},[],[5383],{"type":49,"value":5384},"\u003Cbody>",{"type":49,"value":5386}," tags. HyperFrames loads sub-compositions by injecting their content into the parent document; a full HTML document wrapper prevents the renderer from properly executing GSAP scripts inside the sub-composition, causing all content elements to remain invisible (blank white panels with only background texture showing — audio and captions play normally, making the bug deceptive). Only the root ",{"type":43,"tag":58,"props":5388,"children":5390},{"className":5389},[],[5391],{"type":49,"value":964},{"type":49,"value":5393}," is a full HTML document. Enforced by ",{"type":43,"tag":58,"props":5395,"children":5397},{"className":5396},[],[5398],{"type":49,"value":5399},"scripts\u002Fcheck_composition_format.py",{"type":49,"value":470},{"type":43,"tag":58,"props":5402,"children":5404},{"className":5403},[],[5405],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":5408,"children":5409},{},[5410,5415,5417,5423,5424,5430,5432,5437,5439,5445,5447,5453,5454,5460,5461,5466,5467,5473,5474,5480,5482,5487,5489],{"type":43,"tag":79,"props":5411,"children":5412},{},[5413],{"type":49,"value":5414},"简单符号优先用中文或HTML实体，避免不必要的KaTeX (Prefer Chinese\u002FHTML entities over KaTeX for simple math symbols).",{"type":49,"value":5416}," KaTeX rendering in JS strings requires double-escaped backslashes (",{"type":43,"tag":58,"props":5418,"children":5420},{"className":5419},[],[5421],{"type":49,"value":5422},"\\\\leq",{"type":49,"value":3050},{"type":43,"tag":58,"props":5425,"children":5427},{"className":5426},[],[5428],{"type":49,"value":5429},"\\leq",{"type":49,"value":5431},"); a single-backslash error is ",{"type":43,"tag":79,"props":5433,"children":5434},{},[5435],{"type":49,"value":5436},"silent",{"type":49,"value":5438}," — JS eats ",{"type":43,"tag":58,"props":5440,"children":5442},{"className":5441},[],[5443],{"type":49,"value":5444},"\\l",{"type":49,"value":5446},"→",{"type":43,"tag":58,"props":5448,"children":5450},{"className":5449},[],[5451],{"type":49,"value":5452},"l",{"type":49,"value":455},{"type":43,"tag":58,"props":5455,"children":5457},{"className":5456},[],[5458],{"type":49,"value":5459},"\\a",{"type":49,"value":5446},{"type":43,"tag":58,"props":5462,"children":5464},{"className":5463},[],[5465],{"type":49,"value":765},{"type":49,"value":455},{"type":43,"tag":58,"props":5468,"children":5470},{"className":5469},[],[5471],{"type":49,"value":5472},"\\c",{"type":49,"value":5446},{"type":43,"tag":58,"props":5475,"children":5477},{"className":5476},[],[5478],{"type":49,"value":5479},"c",{"type":49,"value":5481},", producing garbled text like \"OP'leq1\" or \"angleMPNleq90^circ\" instead of proper symbols. ",{"type":43,"tag":79,"props":5483,"children":5484},{},[5485],{"type":49,"value":5486},"To avoid this fragile pipeline, prefer plain HTML or Chinese text whenever the expression does NOT need math layout features",{"type":49,"value":5488}," (fractions, roots, matrices, complex sub\u002Fsuperscripts). Decision table:\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",{"type":43,"tag":102,"props":5490,"children":5491},{},[5492,5518],{"type":43,"tag":106,"props":5493,"children":5494},{},[5495],{"type":43,"tag":110,"props":5496,"children":5497},{},[5498,5503,5508,5513],{"type":43,"tag":114,"props":5499,"children":5500},{},[5501],{"type":49,"value":5502},"Expression type",{"type":43,"tag":114,"props":5504,"children":5505},{},[5506],{"type":49,"value":5507},"Display goal",{"type":43,"tag":114,"props":5509,"children":5510},{},[5511],{"type":49,"value":5512},"Method",{"type":43,"tag":114,"props":5514,"children":5515},{},[5516],{"type":49,"value":5517},"Code",{"type":43,"tag":130,"props":5519,"children":5520},{},[5521,5554,5581,5613,5639,5665,5695,5724,5753,5792,5821,5955,5972,5989],{"type":43,"tag":110,"props":5522,"children":5523},{},[5524,5529,5534,5539],{"type":43,"tag":137,"props":5525,"children":5526},{},[5527],{"type":49,"value":5528},"Simple comparison",{"type":43,"tag":137,"props":5530,"children":5531},{},[5532],{"type":49,"value":5533},"OP' ≤ 1",{"type":43,"tag":137,"props":5535,"children":5536},{},[5537],{"type":49,"value":5538},"HTML entity",{"type":43,"tag":137,"props":5540,"children":5541},{},[5542,5547,5548],{"type":43,"tag":58,"props":5543,"children":5545},{"className":5544},[],[5546],{"type":49,"value":3048},{"type":49,"value":1723},{"type":43,"tag":58,"props":5549,"children":5551},{"className":5550},[],[5552],{"type":49,"value":5553},"OP' &le; 1",{"type":43,"tag":110,"props":5555,"children":5556},{},[5557,5562,5567,5572],{"type":43,"tag":137,"props":5558,"children":5559},{},[5560],{"type":49,"value":5561},"Angle expression",{"type":43,"tag":137,"props":5563,"children":5564},{},[5565],{"type":49,"value":5566},"角MPN ≤ 90°",{"type":43,"tag":137,"props":5568,"children":5569},{},[5570],{"type":49,"value":5571},"Chinese + entity",{"type":43,"tag":137,"props":5573,"children":5574},{},[5575],{"type":43,"tag":58,"props":5576,"children":5578},{"className":5577},[],[5579],{"type":49,"value":5580},"\u003Cspan>角MPN ≤ 90°\u003C\u002Fspan>",{"type":43,"tag":110,"props":5582,"children":5583},{},[5584,5589,5594,5598],{"type":43,"tag":137,"props":5585,"children":5586},{},[5587],{"type":49,"value":5588},"Degree symbol",{"type":43,"tag":137,"props":5590,"children":5591},{},[5592],{"type":49,"value":5593},"90°",{"type":43,"tag":137,"props":5595,"children":5596},{},[5597],{"type":49,"value":5538},{"type":43,"tag":137,"props":5599,"children":5600},{},[5601,5607,5608],{"type":43,"tag":58,"props":5602,"children":5604},{"className":5603},[],[5605],{"type":49,"value":5606},"90&deg;",{"type":49,"value":1723},{"type":43,"tag":58,"props":5609,"children":5611},{"className":5610},[],[5612],{"type":49,"value":5593},{"type":43,"tag":110,"props":5614,"children":5615},{},[5616,5621,5626,5630],{"type":43,"tag":137,"props":5617,"children":5618},{},[5619],{"type":49,"value":5620},"Multiplication",{"type":43,"tag":137,"props":5622,"children":5623},{},[5624],{"type":49,"value":5625},"3 × 4",{"type":43,"tag":137,"props":5627,"children":5628},{},[5629],{"type":49,"value":5538},{"type":43,"tag":137,"props":5631,"children":5632},{},[5633],{"type":43,"tag":58,"props":5634,"children":5636},{"className":5635},[],[5637],{"type":49,"value":5638},"3 &times; 4",{"type":43,"tag":110,"props":5640,"children":5641},{},[5642,5647,5652,5656],{"type":43,"tag":137,"props":5643,"children":5644},{},[5645],{"type":49,"value":5646},"Simple inequality",{"type":43,"tag":137,"props":5648,"children":5649},{},[5650],{"type":49,"value":5651},"x ≥ 5",{"type":43,"tag":137,"props":5653,"children":5654},{},[5655],{"type":49,"value":5538},{"type":43,"tag":137,"props":5657,"children":5658},{},[5659],{"type":43,"tag":58,"props":5660,"children":5662},{"className":5661},[],[5663],{"type":49,"value":5664},"\u003Cspan>x ≥ 5\u003C\u002Fspan>",{"type":43,"tag":110,"props":5666,"children":5667},{},[5668,5673,5678,5686],{"type":43,"tag":137,"props":5669,"children":5670},{},[5671],{"type":49,"value":5672},"Fraction layout",{"type":43,"tag":137,"props":5674,"children":5675},{},[5676],{"type":49,"value":5677},"½",{"type":43,"tag":137,"props":5679,"children":5680},{},[5681],{"type":43,"tag":79,"props":5682,"children":5683},{},[5684],{"type":49,"value":5685},"KaTeX",{"type":43,"tag":137,"props":5687,"children":5688},{},[5689],{"type":43,"tag":58,"props":5690,"children":5692},{"className":5691},[],[5693],{"type":49,"value":5694},"katex.render(\"\\\\\\\\frac{1}{2}\", ...)",{"type":43,"tag":110,"props":5696,"children":5697},{},[5698,5703,5708,5715],{"type":43,"tag":137,"props":5699,"children":5700},{},[5701],{"type":49,"value":5702},"Complex formula",{"type":43,"tag":137,"props":5704,"children":5705},{},[5706],{"type":49,"value":5707},"quadratic",{"type":43,"tag":137,"props":5709,"children":5710},{},[5711],{"type":43,"tag":79,"props":5712,"children":5713},{},[5714],{"type":49,"value":5685},{"type":43,"tag":137,"props":5716,"children":5717},{},[5718],{"type":43,"tag":58,"props":5719,"children":5721},{"className":5720},[],[5722],{"type":49,"value":5723},"katex.render(\"\\\\\\\\frac{-b\\\\\\\\pm\\\\\\\\sqrt{b^2-4ac}}{2a}\", ...)",{"type":43,"tag":110,"props":5725,"children":5726},{},[5727,5732,5737,5744],{"type":43,"tag":137,"props":5728,"children":5729},{},[5730],{"type":49,"value":5731},"Root expression",{"type":43,"tag":137,"props":5733,"children":5734},{},[5735],{"type":49,"value":5736},"√(a²+b²)",{"type":43,"tag":137,"props":5738,"children":5739},{},[5740],{"type":43,"tag":79,"props":5741,"children":5742},{},[5743],{"type":49,"value":5685},{"type":43,"tag":137,"props":5745,"children":5746},{},[5747],{"type":43,"tag":58,"props":5748,"children":5750},{"className":5749},[],[5751],{"type":49,"value":5752},"katex.render(\"\\\\\\\\sqrt{a^2+b^2}\", ...)",{"type":43,"tag":110,"props":5754,"children":5755},{},[5756,5761,5766,5776],{"type":43,"tag":137,"props":5757,"children":5758},{},[5759],{"type":49,"value":5760},"Subscript variable",{"type":43,"tag":137,"props":5762,"children":5763},{},[5764],{"type":49,"value":5765},"x₁, y₂",{"type":43,"tag":137,"props":5767,"children":5768},{},[5769,5771],{"type":49,"value":5770},"HTML ",{"type":43,"tag":58,"props":5772,"children":5774},{"className":5773},[],[5775],{"type":49,"value":3506},{"type":43,"tag":137,"props":5777,"children":5778},{},[5779,5785,5786],{"type":43,"tag":58,"props":5780,"children":5782},{"className":5781},[],[5783],{"type":49,"value":5784},"x\u003Csub>1\u003C\u002Fsub>",{"type":49,"value":455},{"type":43,"tag":58,"props":5787,"children":5789},{"className":5788},[],[5790],{"type":49,"value":5791},"y\u003Csub>2\u003C\u002Fsub>",{"type":43,"tag":110,"props":5793,"children":5794},{},[5795,5800,5805,5810],{"type":43,"tag":137,"props":5796,"children":5797},{},[5798],{"type":49,"value":5799},"Greek letter",{"type":43,"tag":137,"props":5801,"children":5802},{},[5803],{"type":49,"value":5804},"α, β, θ",{"type":43,"tag":137,"props":5806,"children":5807},{},[5808],{"type":49,"value":5809},"Unicode char",{"type":43,"tag":137,"props":5811,"children":5812},{},[5813,5819],{"type":43,"tag":58,"props":5814,"children":5816},{"className":5815},[],[5817],{"type":49,"value":5818},"\u003Cspan>α\u003C\u002Fspan>",{"type":49,"value":5820}," — NEVER write \"alpha\"",{"type":43,"tag":110,"props":5822,"children":5823},{},[5824,5946,5949,5952],{"type":43,"tag":137,"props":5825,"children":5826},{},[5827,5829,5835,5836,5841,5842,5848,5849,5854,5855,5861,5862,5867,5868,5874,5875,5880,5881,5887,5888,5893,5894,5900,5901,5906,5907,5913,5914,5919,5920,5926,5927,5932,5933,5939,5940,5945],{"type":49,"value":5828},"Safe HTML entities: ",{"type":43,"tag":58,"props":5830,"children":5832},{"className":5831},[],[5833],{"type":49,"value":5834},"≤",{"type":49,"value":1098},{"type":43,"tag":58,"props":5837,"children":5839},{"className":5838},[],[5840],{"type":49,"value":3405},{"type":49,"value":4034},{"type":43,"tag":58,"props":5843,"children":5845},{"className":5844},[],[5846],{"type":49,"value":5847},"≥",{"type":49,"value":1098},{"type":43,"tag":58,"props":5850,"children":5852},{"className":5851},[],[5853],{"type":49,"value":3413},{"type":49,"value":4034},{"type":43,"tag":58,"props":5856,"children":5858},{"className":5857},[],[5859],{"type":49,"value":5860},"≠",{"type":49,"value":1098},{"type":43,"tag":58,"props":5863,"children":5865},{"className":5864},[],[5866],{"type":49,"value":3421},{"type":49,"value":4034},{"type":43,"tag":58,"props":5869,"children":5871},{"className":5870},[],[5872],{"type":49,"value":5873},"°",{"type":49,"value":1098},{"type":43,"tag":58,"props":5876,"children":5878},{"className":5877},[],[5879],{"type":49,"value":3429},{"type":49,"value":4034},{"type":43,"tag":58,"props":5882,"children":5884},{"className":5883},[],[5885],{"type":49,"value":5886},"×",{"type":49,"value":1098},{"type":43,"tag":58,"props":5889,"children":5891},{"className":5890},[],[5892],{"type":49,"value":3437},{"type":49,"value":4034},{"type":43,"tag":58,"props":5895,"children":5897},{"className":5896},[],[5898],{"type":49,"value":5899},"÷",{"type":49,"value":1098},{"type":43,"tag":58,"props":5902,"children":5904},{"className":5903},[],[5905],{"type":49,"value":3445},{"type":49,"value":4034},{"type":43,"tag":58,"props":5908,"children":5910},{"className":5909},[],[5911],{"type":49,"value":5912},"±",{"type":49,"value":1098},{"type":43,"tag":58,"props":5915,"children":5917},{"className":5916},[],[5918],{"type":49,"value":3453},{"type":49,"value":4034},{"type":43,"tag":58,"props":5921,"children":5923},{"className":5922},[],[5924],{"type":49,"value":5925},"²",{"type":49,"value":1098},{"type":43,"tag":58,"props":5928,"children":5930},{"className":5929},[],[5931],{"type":49,"value":3461},{"type":49,"value":4034},{"type":43,"tag":58,"props":5934,"children":5936},{"className":5935},[],[5937],{"type":49,"value":5938},"³",{"type":49,"value":1098},{"type":43,"tag":58,"props":5941,"children":5943},{"className":5942},[],[5944],{"type":49,"value":3469},{"type":49,"value":1209},{"type":43,"tag":137,"props":5947,"children":5948},{},[],{"type":43,"tag":137,"props":5950,"children":5951},{},[],{"type":43,"tag":137,"props":5953,"children":5954},{},[],{"type":43,"tag":110,"props":5956,"children":5957},{},[5958,5963,5966,5969],{"type":43,"tag":137,"props":5959,"children":5960},{},[5961],{"type":49,"value":5962},"Safe Unicode Greek letters (in Inter font): α, β, γ, δ, ε, θ, λ, μ, π, φ, ω — use directly, never write English names.",{"type":43,"tag":137,"props":5964,"children":5965},{},[],{"type":43,"tag":137,"props":5967,"children":5968},{},[],{"type":43,"tag":137,"props":5970,"children":5971},{},[],{"type":43,"tag":110,"props":5973,"children":5974},{},[5975,5980,5983,5986],{"type":43,"tag":137,"props":5976,"children":5977},{},[5978],{"type":49,"value":5979},"Chinese replacements for geometric symbols: \"角\" (∠), \"三角形\" (△), \"垂直于\" (⊥), \"平行于\" (∥).",{"type":43,"tag":137,"props":5981,"children":5982},{},[],{"type":43,"tag":137,"props":5984,"children":5985},{},[],{"type":43,"tag":137,"props":5987,"children":5988},{},[],{"type":43,"tag":110,"props":5990,"children":5991},{},[5992,6000,6003,6006],{"type":43,"tag":137,"props":5993,"children":5994},{},[5995],{"type":43,"tag":79,"props":5996,"children":5997},{},[5998],{"type":49,"value":5999},"Rule of thumb: if the expression is a simple comparison, a standalone symbol, or a Chinese-friendly geometric term, use HTML\u002FChinese. If it has fractions, roots, summations, or nested structure, use KaTeX.",{"type":43,"tag":137,"props":6001,"children":6002},{},[],{"type":43,"tag":137,"props":6004,"children":6005},{},[],{"type":43,"tag":137,"props":6007,"children":6008},{},[],{"type":43,"tag":1565,"props":6010,"children":6011},{},[6012,6017,6019,6024,6026,6032,6034,6039,6041,6046,6047,6052,6054,6587,6592,6701,6706,6707,6712],{"type":43,"tag":79,"props":6013,"children":6014},{},[6015],{"type":49,"value":6016},"SVG 坐标轴箭头必须用 orient=\"auto\" + 朝右三角形 (Axis arrows: always use a single right-pointing marker with orient=\"auto\").",{"type":49,"value":6018}," When drawing coordinate axes in SVG, define ONE arrowhead marker with its polygon pointing in the ",{"type":43,"tag":79,"props":6020,"children":6021},{},[6022],{"type":49,"value":6023},"+x direction",{"type":49,"value":6025}," of the marker's local coordinate system, and set ",{"type":43,"tag":58,"props":6027,"children":6029},{"className":6028},[],[6030],{"type":49,"value":6031},"orient=\"auto\"",{"type":49,"value":6033},". This same marker works for ALL axis directions (x-axis, y-axis, or any angle) because ",{"type":43,"tag":58,"props":6035,"children":6037},{"className":6036},[],[6038],{"type":49,"value":6031},{"type":49,"value":6040}," rotates the marker to match the stroke direction at the endpoint. ",{"type":43,"tag":79,"props":6042,"children":6043},{},[6044],{"type":49,"value":6045},"NEVER define a separate \"upward\" marker with a polygon pre-rotated to point up",{"type":49,"value":2003},{"type":43,"tag":58,"props":6048,"children":6050},{"className":6049},[],[6051],{"type":49,"value":6031},{"type":49,"value":6053}," will rotate it an additional -90°, making the arrowhead point LEFT instead of UP. Correct pattern:\n",{"type":43,"tag":571,"props":6055,"children":6057},{"className":3556,"code":6056,"language":3558,"meta":576,"style":576},"\u003Cdefs>\n  \u003C!-- ONE marker for ALL axes — polygon points right (+x), orient=\"auto\" rotates it -->\n  \u003Cmarker id=\"axis-arrow\" markerWidth=\"10\" markerHeight=\"7\" refX=\"10\" refY=\"3.5\" orient=\"auto\">\n    \u003Cpolygon points=\"0 0, 10 3.5, 0 7\" fill=\"#0f172a\"\u002F>\n  \u003C\u002Fmarker>\n\u003C\u002Fdefs>\n\u003C!-- x-axis: line goes right → arrow points right ✅ -->\n\u003Cline x1=\"50\" y1=\"400\" x2=\"570\" y2=\"400\" marker-end=\"url(#axis-arrow)\"\u002F>\n\u003C!-- y-axis: line goes up → orient=\"auto\" rotates arrow to point up ✅ -->\n\u003Cline x1=\"300\" y1=\"470\" x2=\"300\" y2=\"30\" marker-end=\"url(#axis-arrow)\"\u002F>\n",[6058],{"type":43,"tag":58,"props":6059,"children":6060},{"__ignoreMap":576},[6061,6077,6085,6232,6293,6309,6325,6334,6459,6468],{"type":43,"tag":582,"props":6062,"children":6063},{"class":584,"line":585},[6064,6068,6073],{"type":43,"tag":582,"props":6065,"children":6066},{"style":594},[6067],{"type":49,"value":3579},{"type":43,"tag":582,"props":6069,"children":6070},{"style":3582},[6071],{"type":49,"value":6072},"defs",{"type":43,"tag":582,"props":6074,"children":6075},{"style":594},[6076],{"type":49,"value":3643},{"type":43,"tag":582,"props":6078,"children":6079},{"class":584,"line":616},[6080],{"type":43,"tag":582,"props":6081,"children":6082},{"style":3568},[6083],{"type":49,"value":6084},"  \u003C!-- ONE marker for ALL axes — polygon points right (+x), orient=\"auto\" rotates it -->\n",{"type":43,"tag":582,"props":6086,"children":6087},{"class":584,"line":3646},[6088,6093,6098,6102,6106,6110,6115,6119,6124,6128,6132,6137,6141,6146,6150,6154,6159,6163,6168,6172,6176,6180,6184,6189,6193,6197,6202,6206,6211,6215,6219,6224,6228],{"type":43,"tag":582,"props":6089,"children":6090},{"style":594},[6091],{"type":49,"value":6092},"  \u003C",{"type":43,"tag":582,"props":6094,"children":6095},{"style":3582},[6096],{"type":49,"value":6097},"marker",{"type":43,"tag":582,"props":6099,"children":6100},{"style":3587},[6101],{"type":49,"value":3590},{"type":43,"tag":582,"props":6103,"children":6104},{"style":594},[6105],{"type":49,"value":597},{"type":43,"tag":582,"props":6107,"children":6108},{"style":594},[6109],{"type":49,"value":602},{"type":43,"tag":582,"props":6111,"children":6112},{"style":605},[6113],{"type":49,"value":6114},"axis-arrow",{"type":43,"tag":582,"props":6116,"children":6117},{"style":594},[6118],{"type":49,"value":602},{"type":43,"tag":582,"props":6120,"children":6121},{"style":3587},[6122],{"type":49,"value":6123}," markerWidth",{"type":43,"tag":582,"props":6125,"children":6126},{"style":594},[6127],{"type":49,"value":597},{"type":43,"tag":582,"props":6129,"children":6130},{"style":594},[6131],{"type":49,"value":602},{"type":43,"tag":582,"props":6133,"children":6134},{"style":605},[6135],{"type":49,"value":6136},"10",{"type":43,"tag":582,"props":6138,"children":6139},{"style":594},[6140],{"type":49,"value":602},{"type":43,"tag":582,"props":6142,"children":6143},{"style":3587},[6144],{"type":49,"value":6145}," markerHeight",{"type":43,"tag":582,"props":6147,"children":6148},{"style":594},[6149],{"type":49,"value":597},{"type":43,"tag":582,"props":6151,"children":6152},{"style":594},[6153],{"type":49,"value":602},{"type":43,"tag":582,"props":6155,"children":6156},{"style":605},[6157],{"type":49,"value":6158},"7",{"type":43,"tag":582,"props":6160,"children":6161},{"style":594},[6162],{"type":49,"value":602},{"type":43,"tag":582,"props":6164,"children":6165},{"style":3587},[6166],{"type":49,"value":6167}," refX",{"type":43,"tag":582,"props":6169,"children":6170},{"style":594},[6171],{"type":49,"value":597},{"type":43,"tag":582,"props":6173,"children":6174},{"style":594},[6175],{"type":49,"value":602},{"type":43,"tag":582,"props":6177,"children":6178},{"style":605},[6179],{"type":49,"value":6136},{"type":43,"tag":582,"props":6181,"children":6182},{"style":594},[6183],{"type":49,"value":602},{"type":43,"tag":582,"props":6185,"children":6186},{"style":3587},[6187],{"type":49,"value":6188}," refY",{"type":43,"tag":582,"props":6190,"children":6191},{"style":594},[6192],{"type":49,"value":597},{"type":43,"tag":582,"props":6194,"children":6195},{"style":594},[6196],{"type":49,"value":602},{"type":43,"tag":582,"props":6198,"children":6199},{"style":605},[6200],{"type":49,"value":6201},"3.5",{"type":43,"tag":582,"props":6203,"children":6204},{"style":594},[6205],{"type":49,"value":602},{"type":43,"tag":582,"props":6207,"children":6208},{"style":3587},[6209],{"type":49,"value":6210}," orient",{"type":43,"tag":582,"props":6212,"children":6213},{"style":594},[6214],{"type":49,"value":597},{"type":43,"tag":582,"props":6216,"children":6217},{"style":594},[6218],{"type":49,"value":602},{"type":43,"tag":582,"props":6220,"children":6221},{"style":605},[6222],{"type":49,"value":6223},"auto",{"type":43,"tag":582,"props":6225,"children":6226},{"style":594},[6227],{"type":49,"value":602},{"type":43,"tag":582,"props":6229,"children":6230},{"style":594},[6231],{"type":49,"value":3643},{"type":43,"tag":582,"props":6233,"children":6234},{"class":584,"line":3656},[6235,6240,6245,6250,6254,6258,6263,6267,6272,6276,6280,6284,6288],{"type":43,"tag":582,"props":6236,"children":6237},{"style":594},[6238],{"type":49,"value":6239},"    \u003C",{"type":43,"tag":582,"props":6241,"children":6242},{"style":3582},[6243],{"type":49,"value":6244},"polygon",{"type":43,"tag":582,"props":6246,"children":6247},{"style":3587},[6248],{"type":49,"value":6249}," points",{"type":43,"tag":582,"props":6251,"children":6252},{"style":594},[6253],{"type":49,"value":597},{"type":43,"tag":582,"props":6255,"children":6256},{"style":594},[6257],{"type":49,"value":602},{"type":43,"tag":582,"props":6259,"children":6260},{"style":605},[6261],{"type":49,"value":6262},"0 0, 10 3.5, 0 7",{"type":43,"tag":582,"props":6264,"children":6265},{"style":594},[6266],{"type":49,"value":602},{"type":43,"tag":582,"props":6268,"children":6269},{"style":3587},[6270],{"type":49,"value":6271}," fill",{"type":43,"tag":582,"props":6273,"children":6274},{"style":594},[6275],{"type":49,"value":597},{"type":43,"tag":582,"props":6277,"children":6278},{"style":594},[6279],{"type":49,"value":602},{"type":43,"tag":582,"props":6281,"children":6282},{"style":605},[6283],{"type":49,"value":3310},{"type":43,"tag":582,"props":6285,"children":6286},{"style":594},[6287],{"type":49,"value":602},{"type":43,"tag":582,"props":6289,"children":6290},{"style":594},[6291],{"type":49,"value":6292},"\u002F>\n",{"type":43,"tag":582,"props":6294,"children":6295},{"class":584,"line":3665},[6296,6301,6305],{"type":43,"tag":582,"props":6297,"children":6298},{"style":594},[6299],{"type":49,"value":6300},"  \u003C\u002F",{"type":43,"tag":582,"props":6302,"children":6303},{"style":3582},[6304],{"type":49,"value":6097},{"type":43,"tag":582,"props":6306,"children":6307},{"style":594},[6308],{"type":49,"value":3643},{"type":43,"tag":582,"props":6310,"children":6312},{"class":584,"line":6311},6,[6313,6317,6321],{"type":43,"tag":582,"props":6314,"children":6315},{"style":594},[6316],{"type":49,"value":3689},{"type":43,"tag":582,"props":6318,"children":6319},{"style":3582},[6320],{"type":49,"value":6072},{"type":43,"tag":582,"props":6322,"children":6323},{"style":594},[6324],{"type":49,"value":3643},{"type":43,"tag":582,"props":6326,"children":6328},{"class":584,"line":6327},7,[6329],{"type":43,"tag":582,"props":6330,"children":6331},{"style":3568},[6332],{"type":49,"value":6333},"\u003C!-- x-axis: line goes right → arrow points right ✅ -->\n",{"type":43,"tag":582,"props":6335,"children":6337},{"class":584,"line":6336},8,[6338,6342,6346,6351,6355,6359,6364,6368,6373,6377,6381,6386,6390,6395,6399,6403,6408,6412,6417,6421,6425,6429,6433,6438,6442,6446,6451,6455],{"type":43,"tag":582,"props":6339,"children":6340},{"style":594},[6341],{"type":49,"value":3579},{"type":43,"tag":582,"props":6343,"children":6344},{"style":3582},[6345],{"type":49,"value":584},{"type":43,"tag":582,"props":6347,"children":6348},{"style":3587},[6349],{"type":49,"value":6350}," x1",{"type":43,"tag":582,"props":6352,"children":6353},{"style":594},[6354],{"type":49,"value":597},{"type":43,"tag":582,"props":6356,"children":6357},{"style":594},[6358],{"type":49,"value":602},{"type":43,"tag":582,"props":6360,"children":6361},{"style":605},[6362],{"type":49,"value":6363},"50",{"type":43,"tag":582,"props":6365,"children":6366},{"style":594},[6367],{"type":49,"value":602},{"type":43,"tag":582,"props":6369,"children":6370},{"style":3587},[6371],{"type":49,"value":6372}," y1",{"type":43,"tag":582,"props":6374,"children":6375},{"style":594},[6376],{"type":49,"value":597},{"type":43,"tag":582,"props":6378,"children":6379},{"style":594},[6380],{"type":49,"value":602},{"type":43,"tag":582,"props":6382,"children":6383},{"style":605},[6384],{"type":49,"value":6385},"400",{"type":43,"tag":582,"props":6387,"children":6388},{"style":594},[6389],{"type":49,"value":602},{"type":43,"tag":582,"props":6391,"children":6392},{"style":3587},[6393],{"type":49,"value":6394}," x2",{"type":43,"tag":582,"props":6396,"children":6397},{"style":594},[6398],{"type":49,"value":597},{"type":43,"tag":582,"props":6400,"children":6401},{"style":594},[6402],{"type":49,"value":602},{"type":43,"tag":582,"props":6404,"children":6405},{"style":605},[6406],{"type":49,"value":6407},"570",{"type":43,"tag":582,"props":6409,"children":6410},{"style":594},[6411],{"type":49,"value":602},{"type":43,"tag":582,"props":6413,"children":6414},{"style":3587},[6415],{"type":49,"value":6416}," y2",{"type":43,"tag":582,"props":6418,"children":6419},{"style":594},[6420],{"type":49,"value":597},{"type":43,"tag":582,"props":6422,"children":6423},{"style":594},[6424],{"type":49,"value":602},{"type":43,"tag":582,"props":6426,"children":6427},{"style":605},[6428],{"type":49,"value":6385},{"type":43,"tag":582,"props":6430,"children":6431},{"style":594},[6432],{"type":49,"value":602},{"type":43,"tag":582,"props":6434,"children":6435},{"style":3587},[6436],{"type":49,"value":6437}," marker-end",{"type":43,"tag":582,"props":6439,"children":6440},{"style":594},[6441],{"type":49,"value":597},{"type":43,"tag":582,"props":6443,"children":6444},{"style":594},[6445],{"type":49,"value":602},{"type":43,"tag":582,"props":6447,"children":6448},{"style":605},[6449],{"type":49,"value":6450},"url(#axis-arrow)",{"type":43,"tag":582,"props":6452,"children":6453},{"style":594},[6454],{"type":49,"value":602},{"type":43,"tag":582,"props":6456,"children":6457},{"style":594},[6458],{"type":49,"value":6292},{"type":43,"tag":582,"props":6460,"children":6462},{"class":584,"line":6461},9,[6463],{"type":43,"tag":582,"props":6464,"children":6465},{"style":3568},[6466],{"type":49,"value":6467},"\u003C!-- y-axis: line goes up → orient=\"auto\" rotates arrow to point up ✅ -->\n",{"type":43,"tag":582,"props":6469,"children":6471},{"class":584,"line":6470},10,[6472,6476,6480,6484,6488,6492,6497,6501,6505,6509,6513,6518,6522,6526,6530,6534,6538,6542,6546,6550,6554,6559,6563,6567,6571,6575,6579,6583],{"type":43,"tag":582,"props":6473,"children":6474},{"style":594},[6475],{"type":49,"value":3579},{"type":43,"tag":582,"props":6477,"children":6478},{"style":3582},[6479],{"type":49,"value":584},{"type":43,"tag":582,"props":6481,"children":6482},{"style":3587},[6483],{"type":49,"value":6350},{"type":43,"tag":582,"props":6485,"children":6486},{"style":594},[6487],{"type":49,"value":597},{"type":43,"tag":582,"props":6489,"children":6490},{"style":594},[6491],{"type":49,"value":602},{"type":43,"tag":582,"props":6493,"children":6494},{"style":605},[6495],{"type":49,"value":6496},"300",{"type":43,"tag":582,"props":6498,"children":6499},{"style":594},[6500],{"type":49,"value":602},{"type":43,"tag":582,"props":6502,"children":6503},{"style":3587},[6504],{"type":49,"value":6372},{"type":43,"tag":582,"props":6506,"children":6507},{"style":594},[6508],{"type":49,"value":597},{"type":43,"tag":582,"props":6510,"children":6511},{"style":594},[6512],{"type":49,"value":602},{"type":43,"tag":582,"props":6514,"children":6515},{"style":605},[6516],{"type":49,"value":6517},"470",{"type":43,"tag":582,"props":6519,"children":6520},{"style":594},[6521],{"type":49,"value":602},{"type":43,"tag":582,"props":6523,"children":6524},{"style":3587},[6525],{"type":49,"value":6394},{"type":43,"tag":582,"props":6527,"children":6528},{"style":594},[6529],{"type":49,"value":597},{"type":43,"tag":582,"props":6531,"children":6532},{"style":594},[6533],{"type":49,"value":602},{"type":43,"tag":582,"props":6535,"children":6536},{"style":605},[6537],{"type":49,"value":6496},{"type":43,"tag":582,"props":6539,"children":6540},{"style":594},[6541],{"type":49,"value":602},{"type":43,"tag":582,"props":6543,"children":6544},{"style":3587},[6545],{"type":49,"value":6416},{"type":43,"tag":582,"props":6547,"children":6548},{"style":594},[6549],{"type":49,"value":597},{"type":43,"tag":582,"props":6551,"children":6552},{"style":594},[6553],{"type":49,"value":602},{"type":43,"tag":582,"props":6555,"children":6556},{"style":605},[6557],{"type":49,"value":6558},"30",{"type":43,"tag":582,"props":6560,"children":6561},{"style":594},[6562],{"type":49,"value":602},{"type":43,"tag":582,"props":6564,"children":6565},{"style":3587},[6566],{"type":49,"value":6437},{"type":43,"tag":582,"props":6568,"children":6569},{"style":594},[6570],{"type":49,"value":597},{"type":43,"tag":582,"props":6572,"children":6573},{"style":594},[6574],{"type":49,"value":602},{"type":43,"tag":582,"props":6576,"children":6577},{"style":605},[6578],{"type":49,"value":6450},{"type":43,"tag":582,"props":6580,"children":6581},{"style":594},[6582],{"type":49,"value":602},{"type":43,"tag":582,"props":6584,"children":6585},{"style":594},[6586],{"type":49,"value":6292},{"type":43,"tag":79,"props":6588,"children":6589},{},[6590],{"type":49,"value":6591},"Common mistake (y-axis arrow points LEFT):",{"type":43,"tag":571,"props":6593,"children":6595},{"className":3556,"code":6594,"language":3558,"meta":576,"style":576},"\u003C!-- ❌ WRONG — polygon already points up, orient=\"auto\" rotates it -90° more → points LEFT -->\n\u003Cmarker id=\"arrowU\" orient=\"auto\">\u003Cpolygon points=\"0 10, 3.5 0, 7 10\"\u002F>\u003C\u002Fmarker>\n",[6596],{"type":43,"tag":58,"props":6597,"children":6598},{"__ignoreMap":576},[6599,6607],{"type":43,"tag":582,"props":6600,"children":6601},{"class":584,"line":585},[6602],{"type":43,"tag":582,"props":6603,"children":6604},{"style":3568},[6605],{"type":49,"value":6606},"\u003C!-- ❌ WRONG — polygon already points up, orient=\"auto\" rotates it -90° more → points LEFT -->\n",{"type":43,"tag":582,"props":6608,"children":6609},{"class":584,"line":616},[6610,6614,6618,6622,6626,6630,6635,6639,6643,6647,6651,6655,6659,6663,6667,6671,6675,6679,6684,6688,6693,6697],{"type":43,"tag":582,"props":6611,"children":6612},{"style":594},[6613],{"type":49,"value":3579},{"type":43,"tag":582,"props":6615,"children":6616},{"style":3582},[6617],{"type":49,"value":6097},{"type":43,"tag":582,"props":6619,"children":6620},{"style":3587},[6621],{"type":49,"value":3590},{"type":43,"tag":582,"props":6623,"children":6624},{"style":594},[6625],{"type":49,"value":597},{"type":43,"tag":582,"props":6627,"children":6628},{"style":594},[6629],{"type":49,"value":602},{"type":43,"tag":582,"props":6631,"children":6632},{"style":605},[6633],{"type":49,"value":6634},"arrowU",{"type":43,"tag":582,"props":6636,"children":6637},{"style":594},[6638],{"type":49,"value":602},{"type":43,"tag":582,"props":6640,"children":6641},{"style":3587},[6642],{"type":49,"value":6210},{"type":43,"tag":582,"props":6644,"children":6645},{"style":594},[6646],{"type":49,"value":597},{"type":43,"tag":582,"props":6648,"children":6649},{"style":594},[6650],{"type":49,"value":602},{"type":43,"tag":582,"props":6652,"children":6653},{"style":605},[6654],{"type":49,"value":6223},{"type":43,"tag":582,"props":6656,"children":6657},{"style":594},[6658],{"type":49,"value":602},{"type":43,"tag":582,"props":6660,"children":6661},{"style":594},[6662],{"type":49,"value":3698},{"type":43,"tag":582,"props":6664,"children":6665},{"style":3582},[6666],{"type":49,"value":6244},{"type":43,"tag":582,"props":6668,"children":6669},{"style":3587},[6670],{"type":49,"value":6249},{"type":43,"tag":582,"props":6672,"children":6673},{"style":594},[6674],{"type":49,"value":597},{"type":43,"tag":582,"props":6676,"children":6677},{"style":594},[6678],{"type":49,"value":602},{"type":43,"tag":582,"props":6680,"children":6681},{"style":605},[6682],{"type":49,"value":6683},"0 10, 3.5 0, 7 10",{"type":43,"tag":582,"props":6685,"children":6686},{"style":594},[6687],{"type":49,"value":602},{"type":43,"tag":582,"props":6689,"children":6690},{"style":594},[6691],{"type":49,"value":6692},"\u002F>\u003C\u002F",{"type":43,"tag":582,"props":6694,"children":6695},{"style":3582},[6696],{"type":49,"value":6097},{"type":43,"tag":582,"props":6698,"children":6699},{"style":594},[6700],{"type":49,"value":3643},{"type":43,"tag":79,"props":6702,"children":6703},{},[6704],{"type":49,"value":6705},"Why:",{"type":49,"value":343},{"type":43,"tag":58,"props":6708,"children":6710},{"className":6709},[],[6711],{"type":49,"value":6031},{"type":49,"value":6713}," aligns the marker's local +x axis with the line's tangent direction. For a line from (300,470) to (300,30), the tangent is (0,−1) = upward, so the local +x rotates −90°. A right-pointing triangle (+x direction) correctly becomes upward. An already-upward triangle (−y direction) gets rotated to point left.",{"type":43,"tag":1565,"props":6715,"children":6716},{},[6717,6736,6738,6743,6745,6750,6752,6758,6760,6766,6768,6781,6783,6788,6790,6795,6797,6801,6803,6809,6811,6816,6817,6827,6829,6835,6837,6843,6844,6849],{"type":43,"tag":79,"props":6718,"children":6719},{},[6720,6722,6728,6730,6735],{"type":49,"value":6721},"受力箭头\u002F坐标轴禁止加辉光滤镜 (NEVER apply a glow\u002Fblur ",{"type":43,"tag":58,"props":6723,"children":6725},{"className":6724},[],[6726],{"type":49,"value":6727},"filter",{"type":49,"value":6729}," to an axis-aligned ",{"type":43,"tag":58,"props":6731,"children":6733},{"className":6732},[],[6734],{"type":49,"value":1329},{"type":49,"value":1209},{"type":49,"value":6737}," A perfectly horizontal or vertical ",{"type":43,"tag":58,"props":6739,"children":6741},{"className":6740},[],[6742],{"type":49,"value":1329},{"type":49,"value":6744}," — every force arrow (重力 G↓ \u002F 支持力 N↑ \u002F 摩擦力 f \u002F 拉力), coordinate axis, horizontal light ray, or wire — has a ",{"type":43,"tag":79,"props":6746,"children":6747},{},[6748],{"type":49,"value":6749},"degenerate bounding box",{"type":49,"value":6751}," (zero height when horizontal, zero width when vertical). SVG filters default to ",{"type":43,"tag":58,"props":6753,"children":6755},{"className":6754},[],[6756],{"type":49,"value":6757},"filterUnits=\"objectBoundingBox\"",{"type":49,"value":6759}," with a region of ",{"type":43,"tag":58,"props":6761,"children":6763},{"className":6762},[],[6764],{"type":49,"value":6765},"-10%…120%",{"type":49,"value":6767}," of the bbox, so a zero dimension makes the ",{"type":43,"tag":79,"props":6769,"children":6770},{},[6771,6773,6779],{"type":49,"value":6772},"filter region zero-area → the line AND its ",{"type":43,"tag":58,"props":6774,"children":6776},{"className":6775},[],[6777],{"type":49,"value":6778},"marker-end",{"type":49,"value":6780}," arrowhead render completely invisible.",{"type":49,"value":6782}," The ",{"type":43,"tag":58,"props":6784,"children":6786},{"className":6785},[],[6787],{"type":49,"value":1747},{"type":49,"value":6789}," label carries no filter, so it still shows — producing the deceptive symptom ",{"type":43,"tag":79,"props":6791,"children":6792},{},[6793],{"type":49,"value":6794},"\"受力示意图里只有力的字(G\u002FN\u002Fv)，没有力的箭头线条.\"",{"type":49,"value":6796}," This bug hides on slanted rays (a diagonal line has a nonzero-area bbox and survives), so it only surfaces on vertical\u002Fhorizontal force arrows. ",{"type":43,"tag":79,"props":6798,"children":6799},{},[6800],{"type":49,"value":2723},{"type":49,"value":6802}," give force arrows a solid ",{"type":43,"tag":58,"props":6804,"children":6806},{"className":6805},[],[6807],{"type":49,"value":6808},"stroke",{"type":49,"value":6810}," (width 4–6) + ",{"type":43,"tag":58,"props":6812,"children":6814},{"className":6813},[],[6815],{"type":49,"value":6778},{"type":49,"value":1139},{"type":43,"tag":79,"props":6818,"children":6819},{},[6820,6822],{"type":49,"value":6821},"NO ",{"type":43,"tag":58,"props":6823,"children":6825},{"className":6824},[],[6826],{"type":49,"value":6727},{"type":49,"value":6828}," — a force arrow reads perfectly without a glow. If a glow on straight strokes is truly needed, make the filter absolute so it ignores the bbox: ",{"type":43,"tag":58,"props":6830,"children":6832},{"className":6831},[],[6833],{"type":49,"value":6834},"\u003Cfilter id=\"fGlow\" filterUnits=\"userSpaceOnUse\" x=\"0\" y=\"0\" width=\"\u003CviewBoxW>\" height=\"\u003CviewBoxH>\">…\u003C\u002Ffilter>",{"type":49,"value":6836},". Glow filters remain safe on circles\u002Fparticles\u002Fions\u002Ftext (non-degenerate bbox). Enforced by ",{"type":43,"tag":58,"props":6838,"children":6840},{"className":6839},[],[6841],{"type":49,"value":6842},"scripts\u002Fcheck_svg_filter_bbox.py",{"type":49,"value":470},{"type":43,"tag":58,"props":6845,"children":6847},{"className":6846},[],[6848],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":6851,"children":6852},{},[6853,6865,6867,6872,6874,6880,6881,6887,6889,6895,6897,6903,6904,6910,6912,6918,6920,6926,6928,6932,6933,6938,6940,6945,6947,6953,6955,6960,6962,6967,6969,6990,6992,6997,6999,7004,7006,7012,7013,7019,7021,7026,7028,7033,7034,7039,7041,7047],{"type":43,"tag":79,"props":6854,"children":6855},{},[6856,6858,6863],{"type":49,"value":6857},"SVG 标签禁止相互重叠 (SVG ",{"type":43,"tag":58,"props":6859,"children":6861},{"className":6860},[],[6862],{"type":49,"value":1747},{"type":49,"value":6864}," labels must NOT overlap each other).",{"type":49,"value":6866}," SVG ",{"type":43,"tag":58,"props":6868,"children":6870},{"className":6869},[],[6871],{"type":49,"value":1747},{"type":49,"value":6873}," is positioned by hand (absolute ",{"type":43,"tag":58,"props":6875,"children":6877},{"className":6876},[],[6878],{"type":49,"value":6879},"x",{"type":49,"value":1697},{"type":43,"tag":58,"props":6882,"children":6884},{"className":6883},[],[6885],{"type":49,"value":6886},"y",{"type":49,"value":6888}," in viewBox units), so labels crammed around a shared point or along a short line collide and the text mashes together (文字重叠). The classic failure is a pulley\u002Flever diagram that places the fulcrum label ",{"type":43,"tag":58,"props":6890,"children":6892},{"className":6891},[],[6893],{"type":49,"value":6894},"支点 O",{"type":49,"value":6896}," and the two arm labels ",{"type":43,"tag":58,"props":6898,"children":6900},{"className":6899},[],[6901],{"type":49,"value":6902},"动力臂 r",{"type":49,"value":190},{"type":43,"tag":58,"props":6905,"children":6907},{"className":6906},[],[6908],{"type":49,"value":6909},"阻力臂 r",{"type":49,"value":6911}," on the SAME horizontal diameter through the circle center — three centered labels in ~90px of room overlap into \"动力臂 支点O 阻力臂 r\". This class of overlap is invisible to the other layout gates (",{"type":43,"tag":58,"props":6913,"children":6915},{"className":6914},[],[6916],{"type":49,"value":6917},"check_scene_overflow.py",{"type":49,"value":6919}," ignores SVG user coordinates; ",{"type":43,"tag":58,"props":6921,"children":6923},{"className":6922},[],[6924],{"type":49,"value":6925},"check_scene_layout.py",{"type":49,"value":6927}," only checks the DOM wrapper). ",{"type":43,"tag":79,"props":6929,"children":6930},{},[6931],{"type":49,"value":4750},{"type":49,"value":4752},{"type":43,"tag":79,"props":6934,"children":6935},{},[6936],{"type":49,"value":6937},"never stack multiple labels on a shared point or a short segment",{"type":49,"value":6939}," — give each label its own side with a clear gap of ",{"type":43,"tag":79,"props":6941,"children":6942},{},[6943],{"type":49,"value":6944},"≥ ~1 label-height",{"type":49,"value":6946}," (≥ the label's ",{"type":43,"tag":58,"props":6948,"children":6950},{"className":6949},[],[6951],{"type":49,"value":6952},"font-size",{"type":49,"value":6954}," in viewBox units) between neighbouring boxes; (b) for a center\u002Ffulcrum + two arm labels, put the ",{"type":43,"tag":79,"props":6956,"children":6957},{},[6958],{"type":49,"value":6959},"center label ABOVE the line",{"type":49,"value":6961}," and the ",{"type":43,"tag":79,"props":6963,"children":6964},{},[6965],{"type":49,"value":6966},"arm labels BELOW it",{"type":49,"value":6968}," (or vice-versa) so their vertical bands don't collide; (c) ",{"type":43,"tag":79,"props":6970,"children":6971},{},[6972,6974,6980,6982,6988],{"type":49,"value":6973},"anchor arm\u002Fsegment labels ",{"type":43,"tag":58,"props":6975,"children":6977},{"className":6976},[],[6978],{"type":49,"value":6979},"text-anchor=\"end\"",{"type":49,"value":6981}," (left side) \u002F ",{"type":43,"tag":58,"props":6983,"children":6985},{"className":6984},[],[6986],{"type":49,"value":6987},"\"start\"",{"type":49,"value":6989}," (right side)",{"type":49,"value":6991}," and push them ",{"type":43,"tag":79,"props":6993,"children":6994},{},[6995],{"type":49,"value":6996},"outward past the shape edge",{"type":49,"value":6998}," rather than toward the crowded center; (d) offset a label ",{"type":43,"tag":79,"props":7000,"children":7001},{},[7002],{"type":49,"value":7003},"perpendicular to the line\u002Farm it names",{"type":49,"value":7005},", not along it; (e) when two point labels sit on the same axis (e.g. ",{"type":43,"tag":58,"props":7007,"children":7009},{"className":7008},[],[7010],{"type":49,"value":7011},"A(-3,0)",{"type":49,"value":1139},{"type":43,"tag":58,"props":7014,"children":7016},{"className":7015},[],[7017],{"type":49,"value":7018},"E(-5,0)",{"type":49,"value":7020},"), stagger their ",{"type":43,"tag":58,"props":7022,"children":7024},{"className":7023},[],[7025],{"type":49,"value":6886},{"type":49,"value":7027}," or shorten to bare letters so their boxes clear. Do NOT let a point label sit on top of an axis tick number — move the tick or drop the redundant one. Enforced by ",{"type":43,"tag":58,"props":7029,"children":7031},{"className":7030},[],[7032],{"type":49,"value":2191},{"type":49,"value":470},{"type":43,"tag":58,"props":7035,"children":7037},{"className":7036},[],[7038],{"type":49,"value":264},{"type":49,"value":7040}," (conservative: flags only substantial 2-D overlap, accounts for ",{"type":43,"tag":58,"props":7042,"children":7044},{"className":7043},[],[7045],{"type":49,"value":7046},"\u003Cg transform=\"translate\">",{"type":49,"value":7048}," and GSAP-repositioned elements).",{"type":43,"tag":1565,"props":7050,"children":7051},{},[7052,7057,7059,7065,7067,7072,7074,7080,7082,7088,7090,7096,7098,7102,7103,7109,7111,7116,7118,7123,7125,7131,7133,7139,7141,7146,7148,7152,7154,7159,7161,7167,7169,7174,7176,7182,7184,7190,7192,7198,7200,7206,7207,7212],{"type":43,"tag":79,"props":7053,"children":7054},{},[7055],{"type":49,"value":7056},"受力\u002F矢量箭头必须正确 (SVG force\u002Fvector arrows — fixed-size head, full-length line).",{"type":49,"value":7058}," Hand-rolled SVG arrows (",{"type":43,"tag":58,"props":7060,"children":7062},{"className":7061},[],[7063],{"type":49,"value":7064},"\u003Cline marker-end>",{"type":49,"value":7066},") fail in three severe ways at once, as seen in a 蹦床受力 scene (giant sideways arrowheads with no shaft): (a) ",{"type":43,"tag":79,"props":7068,"children":7069},{},[7070],{"type":49,"value":7071},"箭头太大",{"type":49,"value":7073}," — a ",{"type":43,"tag":58,"props":7075,"children":7077},{"className":7076},[],[7078],{"type":49,"value":7079},"\u003Cmarker>",{"type":49,"value":7081}," with no ",{"type":43,"tag":58,"props":7083,"children":7085},{"className":7084},[],[7086],{"type":49,"value":7087},"markerUnits",{"type":49,"value":7089}," defaults to ",{"type":43,"tag":58,"props":7091,"children":7093},{"className":7092},[],[7094],{"type":49,"value":7095},"markerUnits=\"strokeWidth\"",{"type":49,"value":7097},", so the arrowhead is multiplied by the (thick, 6–8) force stroke → a ~72–96px triangle. ",{"type":43,"tag":79,"props":7099,"children":7100},{},[7101],{"type":49,"value":2723},{"type":49,"value":343},{"type":43,"tag":58,"props":7104,"children":7106},{"className":7105},[],[7107],{"type":49,"value":7108},"\u003Cmarker markerUnits=\"userSpaceOnUse\" markerWidth=\"14\" markerHeight=\"14\" refX=… refY=… orient=\"auto\">",{"type":49,"value":7110}," sized in absolute px. (b) ",{"type":43,"tag":79,"props":7112,"children":7113},{},[7114],{"type":49,"value":7115},"方向不对 + (c) 线没画出来",{"type":49,"value":7117}," — drawing the ",{"type":43,"tag":58,"props":7119,"children":7121},{"className":7120},[],[7122],{"type":49,"value":1329},{"type":49,"value":7124}," collapsed to a point (",{"type":43,"tag":58,"props":7126,"children":7128},{"className":7127},[],[7129],{"type":49,"value":7130},"x1==x2 && y1==y2",{"type":49,"value":7132},") and growing it via GSAP ",{"type":43,"tag":58,"props":7134,"children":7136},{"className":7135},[],[7137],{"type":49,"value":7138},"attr:{y2}",{"type":49,"value":7140},": a zero-length line has no shaft, and ",{"type":43,"tag":58,"props":7142,"children":7144},{"className":7143},[],[7145],{"type":49,"value":6031},{"type":49,"value":7147}," on it is undefined so the head points sideways; if the attr tween doesn't seek at render, the shaft never appears. ",{"type":43,"tag":79,"props":7149,"children":7150},{},[7151],{"type":49,"value":2723},{"type":49,"value":7153}," draw the line at its ",{"type":43,"tag":79,"props":7155,"children":7156},{},[7157],{"type":49,"value":7158},"full, correct length",{"type":49,"value":7160}," (so orient is right and the shaft always exists) and reveal it with ",{"type":43,"tag":58,"props":7162,"children":7164},{"className":7163},[],[7165],{"type":49,"value":7166},"stroke-dashoffset",{"type":49,"value":7168}," draw-on — never animate a marker line's length from zero. ",{"type":43,"tag":79,"props":7170,"children":7171},{},[7172],{"type":49,"value":7173},"BEST:",{"type":49,"value":7175}," reuse the prebuilt CSS ",{"type":43,"tag":58,"props":7177,"children":7179},{"className":7178},[],[7180],{"type":49,"value":7181},"force-arrow",{"type":49,"value":7183}," component (",{"type":43,"tag":58,"props":7185,"children":7187},{"className":7186},[],[7188],{"type":49,"value":7189},"assets\u002Fcomponents\u002Fmechanics\u002Fforce-arrow.html",{"type":49,"value":7191},") — fixed head, rotate for direction, ",{"type":43,"tag":58,"props":7193,"children":7195},{"className":7194},[],[7196],{"type":49,"value":7197},"--arrow-length",{"type":49,"value":7199}," for magnitude. Enforced by ",{"type":43,"tag":58,"props":7201,"children":7203},{"className":7202},[],[7204],{"type":49,"value":7205},"scripts\u002Fcheck_svg_arrow.py",{"type":49,"value":470},{"type":43,"tag":58,"props":7208,"children":7210},{"className":7209},[],[7211],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":7214,"children":7215},{},[7216,7221,7223,7228,7230,7236,7238,7242,7244,7249,7251,7256,7258,7263,7265,7276,7278,7284,7286,7291,7293,7298,7300,7305,7307,7312,7314,7320,7321,7326],{"type":43,"tag":79,"props":7217,"children":7218},{},[7219],{"type":49,"value":7220},"图形标签禁止压在图形上 (SVG labels must NOT overlap the drawing — 文字不压线不压点).",{"type":49,"value":7222}," A point\u002Fvertex\u002Fcurve letter (A\u002FB\u002FO\u002FF\u002FM…) must never sit ON a line, axis, symmetry line, curve, or vertex dot — it must be ",{"type":43,"tag":79,"props":7224,"children":7225},{},[7226],{"type":49,"value":7227},"offset OUTWARD, away from the figure, by ~18–24 units",{"type":49,"value":7229},". Classic failure: a point ",{"type":43,"tag":58,"props":7231,"children":7233},{"className":7232},[],[7234],{"type":49,"value":7235},"M",{"type":49,"value":7237}," on the symmetry axis gets its label placed straddling that axis's x, so the dashed axis line runs straight through the \"M\". ",{"type":43,"tag":79,"props":7239,"children":7240},{},[7241],{"type":49,"value":4750},{"type":49,"value":7243}," (a) place a small dot at the point and the ",{"type":43,"tag":79,"props":7245,"children":7246},{},[7247],{"type":49,"value":7248},"letter ~20u beside it, on the side away from the shape centre",{"type":49,"value":7250}," (never at the exact point coordinate); (b) a point on the ",{"type":43,"tag":79,"props":7252,"children":7253},{},[7254],{"type":49,"value":7255},"x-axis",{"type":49,"value":7257}," → label ~22u ",{"type":43,"tag":79,"props":7259,"children":7260},{},[7261],{"type":49,"value":7262},"below\u002Fabove",{"type":49,"value":7264}," the axis, not on it; the ",{"type":43,"tag":79,"props":7266,"children":7267},{},[7268,7270],{"type":49,"value":7269},"origin ",{"type":43,"tag":58,"props":7271,"children":7273},{"className":7272},[],[7274],{"type":49,"value":7275},"O",{"type":49,"value":7277}," → offset into a quadrant (",{"type":43,"tag":58,"props":7279,"children":7281},{"className":7280},[],[7282],{"type":49,"value":7283},"x−14, y+20",{"type":49,"value":7285},"); (c) a label near a ",{"type":43,"tag":79,"props":7287,"children":7288},{},[7289],{"type":49,"value":7290},"vertical\u002Fsymmetry line",{"type":49,"value":7292}," → anchor ",{"type":43,"tag":58,"props":7294,"children":7296},{"className":7295},[],[7297],{"type":49,"value":2176},{"type":49,"value":7299}," and push left (or ",{"type":43,"tag":58,"props":7301,"children":7303},{"className":7302},[],[7304],{"type":49,"value":2183},{"type":49,"value":7306},"\u002Fright) so the stroke clears the glyphs; (d) a ",{"type":43,"tag":79,"props":7308,"children":7309},{},[7310],{"type":49,"value":7311},"curve",{"type":49,"value":7313}," label sits in the empty area beside the curve. Nudging a label a little to clear the figure is expected and encouraged. This is distinct from label-vs-label overlap (Rule #28) — here the collision is label-vs-geometry. Enforced by ",{"type":43,"tag":58,"props":7315,"children":7317},{"className":7316},[],[7318],{"type":49,"value":7319},"scripts\u002Fcheck_svg_label_on_figure.py",{"type":49,"value":470},{"type":43,"tag":58,"props":7322,"children":7324},{"className":7323},[],[7325],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":7328,"children":7329},{},[7330,7335,7337,7343,7345,7349,7350,7362,7363,7369,7371,7377,7379,7385,7387,7393,7395,7400,7402,7414,7416,7421,7423,7428,7430,7436,7438,7443,7445,7451,7453,7458,7460,7465,7466,7471],{"type":43,"tag":79,"props":7331,"children":7332},{},[7333],{"type":49,"value":7334},"关系图\u002F流程图节点必须打组、箭头不压框、图形内白字必显 (SVG node-and-connector diagrams).",{"type":49,"value":7336}," When a scene draws a food chain \u002F feedback loop \u002F flow diagram from SVG boxes + arrows + in-shape labels, three failures ruin it (seen in a 生态平衡 case: white ",{"type":43,"tag":58,"props":7338,"children":7340},{"className":7339},[],[7341],{"type":49,"value":7342},"负反馈",{"type":49,"value":7344}," invisible, arrows poking into the 方框). ",{"type":43,"tag":79,"props":7346,"children":7347},{},[7348],{"type":49,"value":4750},{"type":49,"value":4752},{"type":43,"tag":79,"props":7351,"children":7352},{},[7353,7355,7360],{"type":49,"value":7354},"a label inside a shape MUST be in the SAME ",{"type":43,"tag":58,"props":7356,"children":7358},{"className":7357},[],[7359],{"type":49,"value":2207},{"type":49,"value":7361}," as that shape",{"type":49,"value":2003},{"type":43,"tag":58,"props":7364,"children":7366},{"className":7365},[],[7367],{"type":49,"value":7368},"\u003Cg>\u003Ccircle\u002F>\u003Ctext\u002F>\u003C\u002Fg>",{"type":49,"value":7370},", never independent siblings; otherwise a GSAP ",{"type":43,"tag":58,"props":7372,"children":7374},{"className":7373},[],[7375],{"type":49,"value":7376},"scale",{"type":49,"value":7378},"\u002Fpulse\u002F",{"type":43,"tag":58,"props":7380,"children":7382},{"className":7381},[],[7383],{"type":49,"value":7384},"back.out",{"type":49,"value":7386}," overshoot or ",{"type":43,"tag":58,"props":7388,"children":7390},{"className":7389},[],[7391],{"type":49,"value":7392},"svgOrigin",{"type":49,"value":7394}," residue moves the shape but not the text, so the label ",{"type":43,"tag":79,"props":7396,"children":7397},{},[7398],{"type":49,"value":7399},"slides off the shape onto the panel and disappears",{"type":49,"value":7401}," (the \"白色文字没显示\" bug). Animate\u002Fposition the group, not the shape alone. (b) ",{"type":43,"tag":79,"props":7403,"children":7404},{},[7405,7407,7412],{"type":49,"value":7406},"white SVG ",{"type":43,"tag":58,"props":7408,"children":7410},{"className":7409},[],[7411],{"type":49,"value":1747},{"type":49,"value":7413}," is allowed ONLY inside a dark shape that contains it",{"type":49,"value":7415}," (white-on-light is invisible; ",{"type":43,"tag":58,"props":7417,"children":7419},{"className":7418},[],[7420],{"type":49,"value":2269},{"type":49,"value":7422}," text is otherwise forbidden — see Rule on light-theme colors), and it must be grouped with that shape per (a); size the shape so the text fits with margin. (c) ",{"type":43,"tag":79,"props":7424,"children":7425},{},[7426],{"type":49,"value":7427},"arrow\u002Fconnector endpoints stop ≥ ~10u before the target box edge",{"type":49,"value":7429}," (account for ",{"type":43,"tag":58,"props":7431,"children":7433},{"className":7432},[],[7434],{"type":49,"value":7435},"markerWidth",{"type":49,"value":7437},") — never end a ",{"type":43,"tag":58,"props":7439,"children":7441},{"className":7440},[],[7442],{"type":49,"value":6778},{"type":49,"value":7444}," line\u002Fpath inside or flush against a ",{"type":43,"tag":58,"props":7446,"children":7448},{"className":7447},[],[7449],{"type":49,"value":7450},"\u003Crect>",{"type":49,"value":7452}," node, or the arrowhead hides under \u002F pokes into the 方框. (d) ",{"type":43,"tag":79,"props":7454,"children":7455},{},[7456],{"type":49,"value":7457},"edge labels (\"取食\"\u002F\"捕食\") go in the gap between boxes with clearance",{"type":49,"value":7459},", not against a box; space boxes so connector + label + clearances fit. Enforced by ",{"type":43,"tag":58,"props":7461,"children":7463},{"className":7462},[],[7464],{"type":49,"value":2215},{"type":49,"value":470},{"type":43,"tag":58,"props":7467,"children":7469},{"className":7468},[],[7470],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":7473,"children":7474},{},[7475,7488,7490,7495,7497,7502,7504,7509,7511,7516,7518,7524,7525,7530,7532,7537,7539,7545,7546,7550,7551,7556,7558,7563,7565,7571,7573,7579,7581,7586,7588,7593,7594,7599,7601,7606,7607,7613,7615,7620,7622,7627,7629,7635,7637,7643,7645,7650,7652,7657,7659,7664,7666,7671],{"type":43,"tag":79,"props":7476,"children":7477},{},[7478,7480,7486],{"type":49,"value":7479},"SVG 高度必须有界，禁止 ",{"type":43,"tag":58,"props":7481,"children":7483},{"className":7482},[],[7484],{"type":49,"value":7485},"width:100%;height:auto",{"type":49,"value":7487}," 撑爆场景 (SVG height must be bounded — never let width drive an unbounded height).",{"type":49,"value":7489}," A content ",{"type":43,"tag":58,"props":7491,"children":7493},{"className":7492},[],[7494],{"type":49,"value":2081},{"type":49,"value":7496}," styled ",{"type":43,"tag":58,"props":7498,"children":7500},{"className":7499},[],[7501],{"type":49,"value":2089},{"type":49,"value":7503}," sizes its width to the panel (~1380px in a full-width panel) and then, because ",{"type":43,"tag":58,"props":7505,"children":7507},{"className":7506},[],[7508],{"type":49,"value":2097},{"type":49,"value":7510},", sets its ",{"type":43,"tag":79,"props":7512,"children":7513},{},[7514],{"type":49,"value":7515},"height from the viewBox aspect ratio",{"type":49,"value":7517}," — a near-square viewBox like ",{"type":43,"tag":58,"props":7519,"children":7521},{"className":7520},[],[7522],{"type":49,"value":7523},"0 0 1000 760",{"type":49,"value":3108},{"type":43,"tag":79,"props":7526,"children":7527},{},[7528],{"type":49,"value":7529},"~1050px tall",{"type":49,"value":7531},", far past the ~860px usable height (1080 − top padding − 180px caption safe zone). The SVG overflows the frame and the scene looks like it ",{"type":43,"tag":79,"props":7533,"children":7534},{},[7535],{"type":49,"value":7536},"\"suddenly got bigger\" (场景突然变大)",{"type":49,"value":7538}," than the earlier text scenes. This is invisible to the literal-px overflow gate (there is no ",{"type":43,"tag":58,"props":7540,"children":7542},{"className":7541},[],[7543],{"type":49,"value":7544},"px > frame",{"type":49,"value":1368},{"type":43,"tag":79,"props":7547,"children":7548},{},[7549],{"type":49,"value":4750},{"type":49,"value":4752},{"type":43,"tag":79,"props":7552,"children":7553},{},[7554],{"type":49,"value":7555},"never",{"type":49,"value":7557}," write ",{"type":43,"tag":58,"props":7559,"children":7561},{"className":7560},[],[7562],{"type":49,"value":2089},{"type":49,"value":7564}," on a content SVG with a tall\u002Fnear-square viewBox (and equally never ",{"type":43,"tag":58,"props":7566,"children":7568},{"className":7567},[],[7569],{"type":49,"value":7570},"width:100%; aspect-ratio:1",{"type":49,"value":7572}," — a CSS ",{"type":43,"tag":58,"props":7574,"children":7576},{"className":7575},[],[7577],{"type":49,"value":7578},"aspect-ratio",{"type":49,"value":7580}," drives the height from the width just the same); (b) ",{"type":43,"tag":79,"props":7582,"children":7583},{},[7584],{"type":49,"value":7585},"bound the height",{"type":49,"value":7587}," — either add ",{"type":43,"tag":58,"props":7589,"children":7591},{"className":7590},[],[7592],{"type":49,"value":2113},{"type":49,"value":1764},{"type":43,"tag":58,"props":7595,"children":7597},{"className":7596},[],[7598],{"type":49,"value":2120},{"type":49,"value":7600},") so the SVG letterboxes, or size it ",{"type":43,"tag":79,"props":7602,"children":7603},{},[7604],{"type":49,"value":7605},"by height",{"type":49,"value":1098},{"type":43,"tag":58,"props":7608,"children":7610},{"className":7609},[],[7611],{"type":49,"value":7612},"height:100%",{"type":49,"value":7614}," inside a bounded flex\u002Fgrid box) and let ",{"type":43,"tag":58,"props":7616,"children":7618},{"className":7617},[],[7619],{"type":49,"value":2128},{"type":49,"value":7621}," shrink the width to fit; (c) alternatively choose a ",{"type":43,"tag":79,"props":7623,"children":7624},{},[7625],{"type":49,"value":7626},"viewBox aspect ratio that matches the available box",{"type":49,"value":7628}," (wide\u002Flandscape, ratio ≲ 0.5) so ",{"type":43,"tag":58,"props":7630,"children":7632},{"className":7631},[],[7633],{"type":49,"value":7634},"width:100%",{"type":49,"value":7636}," keeps the height under ~820px. The origin of the \"后面的渲染场景突然变大\" bug was exactly a 图象验证 (graph) scene with ",{"type":43,"tag":58,"props":7638,"children":7640},{"className":7639},[],[7641],{"type":49,"value":7642},"#svg{width:100%;height:auto}",{"type":49,"value":7644}," on a ",{"type":43,"tag":58,"props":7646,"children":7648},{"className":7647},[],[7649],{"type":49,"value":2105},{"type":49,"value":7651}," viewBox. Enforced deterministically by ",{"type":43,"tag":58,"props":7653,"children":7655},{"className":7654},[],[7656],{"type":49,"value":2136},{"type":49,"value":7658}," (browserless) and measured at runtime by ",{"type":43,"tag":58,"props":7660,"children":7662},{"className":7661},[],[7663],{"type":49,"value":2144},{"type":49,"value":7665},", both in ",{"type":43,"tag":58,"props":7667,"children":7669},{"className":7668},[],[7670],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":1565,"props":7673,"children":7674},{},[7675,7688,7690,7696,7698,7702,7704,7709,7710,7715,7716,7722,7723,7729,7730,7735,7736,7742,7743,7749,7750,7756,7757,7763,7765,7770,7772,7777,7779,7785,7787,7792,7794,7800,7802,7808,7809,7815,7817,7821,7823,7828,7829,7835,7836,7842,7844,7849,7851,7862,7864,7874,7876,7882,7884,7889,7891,7896,7897,7902,7904,7909,7911,7916,7918,8005,8006,8012,8013,8018],{"type":43,"tag":79,"props":7676,"children":7677},{},[7678,7680,7686],{"type":49,"value":7679},"禁止对带定位 ",{"type":43,"tag":58,"props":7681,"children":7683},{"className":7682},[],[7684],{"type":49,"value":7685},"transform",{"type":49,"value":7687}," 属性的 SVG 元素补间 x\u002Fy\u002Frotation\u002Fscale (never GSAP-tween a transform prop on a transform-positioned SVG element — 否则物体飞出画面).",{"type":49,"value":7689}," An SVG element placed with a hard-coded transform attribute, e.g. ",{"type":43,"tag":58,"props":7691,"children":7693},{"className":7692},[],[7694],{"type":49,"value":7695},"\u003Cg transform=\"translate(390,364) rotate(-30)\">",{"type":49,"value":7697}," (a block on an incline, a rotated arrow, a positioned symbol), must ",{"type":43,"tag":79,"props":7699,"children":7700},{},[7701],{"type":49,"value":90},{"type":49,"value":7703}," be the target of a GSAP tween that animates a transform shorthand (",{"type":43,"tag":58,"props":7705,"children":7707},{"className":7706},[],[7708],{"type":49,"value":6879},{"type":49,"value":455},{"type":43,"tag":58,"props":7711,"children":7713},{"className":7712},[],[7714],{"type":49,"value":6886},{"type":49,"value":455},{"type":43,"tag":58,"props":7717,"children":7719},{"className":7718},[],[7720],{"type":49,"value":7721},"rotation",{"type":49,"value":455},{"type":43,"tag":58,"props":7724,"children":7726},{"className":7725},[],[7727],{"type":49,"value":7728},"rotationX\u002FY\u002FZ",{"type":49,"value":455},{"type":43,"tag":58,"props":7731,"children":7733},{"className":7732},[],[7734],{"type":49,"value":7376},{"type":49,"value":455},{"type":43,"tag":58,"props":7737,"children":7739},{"className":7738},[],[7740],{"type":49,"value":7741},"scaleX\u002FY",{"type":49,"value":455},{"type":43,"tag":58,"props":7744,"children":7746},{"className":7745},[],[7747],{"type":49,"value":7748},"xPercent",{"type":49,"value":455},{"type":43,"tag":58,"props":7751,"children":7753},{"className":7752},[],[7754],{"type":49,"value":7755},"yPercent",{"type":49,"value":455},{"type":43,"tag":58,"props":7758,"children":7760},{"className":7759},[],[7761],{"type":49,"value":7762},"skewX\u002FY",{"type":49,"value":7764},"). GSAP parses the element's existing ",{"type":43,"tag":58,"props":7766,"children":7768},{"className":7767},[],[7769],{"type":49,"value":7685},{"type":49,"value":7771}," into its own model, then the tween ",{"type":43,"tag":79,"props":7773,"children":7774},{},[7775],{"type":49,"value":7776},"overwrites",{"type":49,"value":7778}," it — e.g. ",{"type":43,"tag":58,"props":7780,"children":7782},{"className":7781},[],[7783],{"type":49,"value":7784},"fromTo(\"g[transform]\", {y:10}, {y:0})",{"type":49,"value":7786}," drives translateY from its real 364 down to 0, so the element ",{"type":43,"tag":79,"props":7788,"children":7789},{},[7790],{"type":49,"value":7791},"jumps ~364px off its position and flies out of frame",{"type":49,"value":7793}," (物体飞出画面). The classic failure is a broad selector like ",{"type":43,"tag":58,"props":7795,"children":7797},{"className":7796},[],[7798],{"type":49,"value":7799},"g[transform]",{"type":49,"value":7801}," or a bare ",{"type":43,"tag":58,"props":7803,"children":7805},{"className":7804},[],[7806],{"type":49,"value":7807},"g",{"type":49,"value":1697},{"type":43,"tag":58,"props":7810,"children":7812},{"className":7811},[],[7813],{"type":49,"value":7814},"rect",{"type":49,"value":7816}," selector that happens to hit a positioned group. ",{"type":43,"tag":79,"props":7818,"children":7819},{},[7820],{"type":49,"value":4750},{"type":49,"value":7822}," (a) on a transform-positioned SVG element, animate ",{"type":43,"tag":79,"props":7824,"children":7825},{},[7826],{"type":49,"value":7827},"ONLY",{"type":49,"value":343},{"type":43,"tag":58,"props":7830,"children":7832},{"className":7831},[],[7833],{"type":49,"value":7834},"opacity",{"type":49,"value":1697},{"type":43,"tag":58,"props":7837,"children":7839},{"className":7838},[],[7840],{"type":49,"value":7841},"autoAlpha",{"type":49,"value":7843}," (never x\u002Fy\u002Frotation\u002Fscale); (b) to also slide\u002Fscale it, ",{"type":43,"tag":79,"props":7845,"children":7846},{},[7847],{"type":49,"value":7848},"NEST",{"type":49,"value":7850}," — keep the positioning transform on a ",{"type":43,"tag":79,"props":7852,"children":7853},{},[7854,7856],{"type":49,"value":7855},"static outer ",{"type":43,"tag":58,"props":7857,"children":7859},{"className":7858},[],[7860],{"type":49,"value":7861},"\u003Cg transform=...>",{"type":49,"value":7863}," and put the fade\u002Fslide tween on an ",{"type":43,"tag":79,"props":7865,"children":7866},{},[7867,7869],{"type":49,"value":7868},"inner ",{"type":43,"tag":58,"props":7870,"children":7872},{"className":7871},[],[7873],{"type":49,"value":2207},{"type":49,"value":7875}," (or wrapper); (c) if you truly must animate its transform, use ",{"type":43,"tag":58,"props":7877,"children":7879},{"className":7878},[],[7880],{"type":49,"value":7881},"attr:{transform:'translate(...) rotate(...)'}",{"type":49,"value":7883}," with the ",{"type":43,"tag":79,"props":7885,"children":7886},{},[7887],{"type":49,"value":7888},"full",{"type":49,"value":7890}," transform written out at both ends, never a bare ",{"type":43,"tag":58,"props":7892,"children":7894},{"className":7893},[],[7895],{"type":49,"value":6879},{"type":49,"value":1697},{"type":43,"tag":58,"props":7898,"children":7900},{"className":7899},[],[7901],{"type":49,"value":6886},{"type":49,"value":7903}," shorthand; (d) never target positioned groups with a broad ",{"type":43,"tag":58,"props":7905,"children":7907},{"className":7906},[],[7908],{"type":49,"value":7799},{"type":49,"value":7910}," \u002F bare-tag selector. Animating x\u002Fy\u002Fscale on elements ",{"type":43,"tag":79,"props":7912,"children":7913},{},[7914],{"type":49,"value":7915},"without",{"type":49,"value":7917}," a transform attribute (plain circles, HTML panels positioned by CSS) is fine. ",{"type":43,"tag":79,"props":7919,"children":7920},{},[7921,7923,7928,7929,7934,7936,7942,7944,7950,7952,7957,7959,7964,7966,7972,7974,7980,7982,7988,7990,7995,7997,8003],{"type":49,"value":7922},"(e) 对 SVG 元素做 ",{"type":43,"tag":58,"props":7924,"children":7926},{"className":7925},[],[7927],{"type":49,"value":7376},{"type":49,"value":1697},{"type":43,"tag":58,"props":7930,"children":7932},{"className":7931},[],[7933],{"type":49,"value":7721},{"type":49,"value":7935}," 缩放\u002F旋转时，绝不要用 px 单位的 ",{"type":43,"tag":58,"props":7937,"children":7939},{"className":7938},[],[7940],{"type":49,"value":7941},"transformOrigin",{"type":49,"value":7943},"（如 ",{"type":43,"tag":58,"props":7945,"children":7947},{"className":7946},[],[7948],{"type":49,"value":7949},"transformOrigin:\"470px 270px\"",{"type":49,"value":7951},"）——在 SVG 上它按元素包围盒本地坐标换算，对一个子元素坐标远离 SVG 原点的 ",{"type":43,"tag":58,"props":7953,"children":7955},{"className":7954},[],[7956],{"type":49,"value":2207},{"type":49,"value":7958}," 缩放会附带一个很大的平移量，把元素甩飞\u002F错位（即使该 ",{"type":43,"tag":58,"props":7960,"children":7962},{"className":7961},[],[7963],{"type":49,"value":2207},{"type":49,"value":7965}," 没有 transform 属性、纯靠子元素绝对坐标定位，也会飞——这正是\"变阻器缩短动画把整个 R₁ 甩到画面外\"的真实事故）。改用 GSAP 专给 SVG 的 ",{"type":43,"tag":58,"props":7967,"children":7969},{"className":7968},[],[7970],{"type":49,"value":7971},"svgOrigin:\"x y\"",{"type":49,"value":7973},"（用户坐标系，如 ",{"type":43,"tag":58,"props":7975,"children":7977},{"className":7976},[],[7978],{"type":49,"value":7979},"svgOrigin:\"470 270\"",{"type":49,"value":7981},"），或把元素套进 ",{"type":43,"tag":58,"props":7983,"children":7985},{"className":7984},[],[7986],{"type":49,"value":7987},"\u003Cg transform=\"translate(...)\">",{"type":49,"value":7989}," 再对内层缩放。",{"type":43,"tag":58,"props":7991,"children":7993},{"className":7992},[],[7994],{"type":49,"value":7941},{"type":49,"value":7996}," 用百分比（如 ",{"type":43,"tag":58,"props":7998,"children":8000},{"className":7999},[],[8001],{"type":49,"value":8002},"\"50% 50%\"",{"type":49,"value":8004},"）在 SVG 上是安全的。",{"type":49,"value":4707},{"type":43,"tag":58,"props":8007,"children":8009},{"className":8008},[],[8010],{"type":49,"value":8011},"scripts\u002Fcheck_svg_transform_anim.py",{"type":49,"value":470},{"type":43,"tag":58,"props":8014,"children":8016},{"className":8015},[],[8017],{"type":49,"value":264},{"type":49,"value":301},{"type":43,"tag":67,"props":8020,"children":8022},{"id":8021},"golden-examples-few-shot-references",[8023],{"type":49,"value":8024},"Golden Examples (Few-Shot References)",{"type":43,"tag":52,"props":8026,"children":8027},{},[8028,8030,8035],{"type":49,"value":8029},"Before building compositions for a new problem, ",{"type":43,"tag":79,"props":8031,"children":8032},{},[8033],{"type":49,"value":8034},"read the matching golden example",{"type":49,"value":8036}," to see the complete pipeline (PROBLEM → ANALYSIS → SCRIPT → STORYBOARD → HTML compositions → index.html) executed at top quality. These are condensed from real high-scoring videos — no binary files, just the essential pipeline artifacts and representative composition code.",{"type":43,"tag":102,"props":8038,"children":8039},{},[8040,8060],{"type":43,"tag":106,"props":8041,"children":8042},{},[8043],{"type":43,"tag":110,"props":8044,"children":8045},{},[8046,8051,8055],{"type":43,"tag":114,"props":8047,"children":8048},{},[8049],{"type":49,"value":8050},"Example",{"type":43,"tag":114,"props":8052,"children":8053},{},[8054],{"type":49,"value":2888},{"type":43,"tag":114,"props":8056,"children":8057},{},[8058],{"type":49,"value":8059},"When to Read",{"type":43,"tag":130,"props":8061,"children":8062},{},[8063,8088],{"type":43,"tag":110,"props":8064,"children":8065},{},[8066,8073,8078],{"type":43,"tag":137,"props":8067,"children":8068},{},[8069],{"type":43,"tag":765,"props":8070,"children":8071},{"href":1275},[8072],{"type":49,"value":1275},{"type":43,"tag":137,"props":8074,"children":8075},{},[8076],{"type":49,"value":8077},"Geometry proof (旋转、等边三角形、垂直平分线)",{"type":43,"tag":137,"props":8079,"children":8080},{},[8081,8086],{"type":43,"tag":79,"props":8082,"children":8083},{},[8084],{"type":49,"value":8085},"Any geometry proof problem",{"type":49,"value":8087}," — shows coordinate computation, GEOMETRY VERIFICATION blocks, split layout (SVG + note blocks), progressive draw-on animations synced to narration, proof-step overlay patterns",{"type":43,"tag":110,"props":8089,"children":8090},{},[8091,8099,8107],{"type":43,"tag":137,"props":8092,"children":8093},{},[8094],{"type":43,"tag":765,"props":8095,"children":8097},{"href":8096},"references\u002Fgolden-example-interactive-transform.md",[8098],{"type":49,"value":8096},{"type":43,"tag":137,"props":8100,"children":8101},{},[8102],{"type":43,"tag":79,"props":8103,"children":8104},{},[8105],{"type":49,"value":8106},"Interactive HTML explorer (交互式网页, 不是视频)",{"type":43,"tag":137,"props":8108,"children":8109},{},[8110,8115,8117,8122],{"type":43,"tag":79,"props":8111,"children":8112},{},[8113],{"type":49,"value":8114},"When the user asks for an interactive page \u002F 交互式界面 \u002F 让用户拖动体验 (sliders\u002Fcontrols that update a graph AND the equation live), instead of an MP4.",{"type":49,"value":8116}," Ships a complete offline ",{"type":43,"tag":58,"props":8118,"children":8120},{"className":8119},[],[8121],{"type":49,"value":964},{"type":49,"value":8123}," (Canvas graph + KaTeX live equation + dropdown\u002Fsliders\u002Fcheckboxes\u002Freset, Aurora theme) plus the 6 rules to reproduce it exactly. Skip the Step 0–6 video pipeline for this.",{"type":43,"tag":501,"props":8125,"children":8126},{},[8127],{"type":43,"tag":52,"props":8128,"children":8129},{},[8130,8132,8137,8139,8144,8146,8151,8153,8158,8160,8165],{"type":49,"value":8131},"⚙️ ",{"type":43,"tag":79,"props":8133,"children":8134},{},[8135],{"type":49,"value":8136},"Two output modalities.",{"type":49,"value":8138}," Most requests → a narrated ",{"type":43,"tag":79,"props":8140,"children":8141},{},[8142],{"type":49,"value":8143},"MP4",{"type":49,"value":8145}," via the Step 0–6 pipeline (use the\ngeometry \u002F process-animation examples above). But when the user explicitly wants an ",{"type":43,"tag":79,"props":8147,"children":8148},{},[8149],{"type":49,"value":8150},"interactive\nweb page",{"type":49,"value":8152}," (拖动参数、实时联动，不是视频), switch to the ",{"type":43,"tag":79,"props":8154,"children":8155},{},[8156],{"type":49,"value":8157},"interactive",{"type":49,"value":8159}," golden example above — same\nAurora look & offline assets, but the deliverable is a self-contained ",{"type":43,"tag":58,"props":8161,"children":8163},{"className":8162},[],[8164],{"type":49,"value":964},{"type":49,"value":8166}," opened by\ndouble-click, not a rendered video.",{"type":43,"tag":1561,"props":8168,"children":8170},{"start":8169},34,[8171],{"type":43,"tag":1565,"props":8172,"children":8173},{},[8174,8223,8225,8237,8239,8245,8247,8252,8254,8260,8262,8268,8269,8274,8276,8281,8283,8289,8291,8296,8298,8304,8305,8311,8313,8319,8321,8326,8328,8333,8335,8340,8342,8362,8364,8369,8370,8375,8377,8382,8384,8389,8391,8397,8399,8404,8406,8412,8414,8420,8422,8427,8429,8434,8436,8442,8444,8449,8451,8457,8459,8464],{"type":43,"tag":79,"props":8175,"children":8176},{},[8177,8179,8185,8186,8191,8193,8198,8200,8206,8208,8214,8216,8221],{"type":49,"value":8178},"KaTeX 必须在合成加载时【同步】渲染，并按 ",{"type":43,"tag":58,"props":8180,"children":8182},{"className":8181},[],[8183],{"type":49,"value":8184},"id",{"type":49,"value":1697},{"type":43,"tag":58,"props":8187,"children":8189},{"className":8188},[],[8190],{"type":49,"value":5046},{"type":49,"value":8192}," 作用域选择；禁止无 ",{"type":43,"tag":58,"props":8194,"children":8196},{"className":8195},[],[8197],{"type":49,"value":8184},{"type":49,"value":8199}," 的 ",{"type":43,"tag":58,"props":8201,"children":8203},{"className":8202},[],[8204],{"type":49,"value":8205},"#合成id",{"type":49,"value":8207}," 选择器，禁止用 setTimeout 延迟渲染 (render KaTeX synchronously at load, scoped correctly; never a ",{"type":43,"tag":58,"props":8209,"children":8211},{"className":8210},[],[8212],{"type":49,"value":8213},"#composition-id",{"type":49,"value":8215}," selector without a matching ",{"type":43,"tag":58,"props":8217,"children":8219},{"className":8218},[],[8220],{"type":49,"value":8184},{"type":49,"value":8222},", never defer with setTimeout).",{"type":49,"value":8224}," 已确认两类\"公式空白\"坑（画面空白，选项\u002F步骤\u002F公式看不到）：",{"type":43,"tag":79,"props":8226,"children":8227},{},[8228,8230,8235],{"type":49,"value":8229},"(1) 缺根 ",{"type":43,"tag":58,"props":8231,"children":8233},{"className":8232},[],[8234],{"type":49,"value":8184},{"type":49,"value":8236},"（确定性、最常见）",{"type":49,"value":8238}," — 根 div 是 ",{"type":43,"tag":58,"props":8240,"children":8242},{"className":8241},[],[8243],{"type":49,"value":8244},"\u003Cdiv data-composition-id=\"mt-x\">",{"type":49,"value":8246},"（",{"type":43,"tag":58,"props":8248,"children":8250},{"className":8249},[],[8251],{"type":49,"value":5046},{"type":49,"value":8253}," 是 data 属性，不是 DOM id），但渲染循环用 ",{"type":43,"tag":58,"props":8255,"children":8257},{"className":8256},[],[8258],{"type":49,"value":8259},"document.querySelectorAll(\"#mt-x [data-tex]\")",{"type":49,"value":8261},"；",{"type":43,"tag":58,"props":8263,"children":8265},{"className":8264},[],[8266],{"type":49,"value":8267},"#mt-x",{"type":49,"value":343},{"type":43,"tag":79,"props":8270,"children":8271},{},[8272],{"type":49,"value":8273},"匹配 0 个",{"type":49,"value":8275}," → 不渲染 → 公式全空。",{"type":43,"tag":79,"props":8277,"children":8278},{},[8279],{"type":49,"value":8280},"修复",{"type":49,"value":8282},"：给根 div 同时加 ",{"type":43,"tag":58,"props":8284,"children":8286},{"className":8285},[],[8287],{"type":49,"value":8288},"id=\"mt-x\"",{"type":49,"value":8290},"（保留 ",{"type":43,"tag":58,"props":8292,"children":8294},{"className":8293},[],[8295],{"type":49,"value":5046},{"type":49,"value":8297},"），或用 ",{"type":43,"tag":58,"props":8299,"children":8301},{"className":8300},[],[8302],{"type":49,"value":8303},"[data-composition-id=\"mt-x\"]",{"type":49,"value":190},{"type":43,"tag":58,"props":8306,"children":8308},{"className":8307},[],[8309],{"type":49,"value":8310},"document.currentScript.closest(\"[data-composition-id]\")",{"type":49,"value":8312}," 作 id 无关作用域。由 ",{"type":43,"tag":58,"props":8314,"children":8316},{"className":8315},[],[8317],{"type":49,"value":8318},"scripts\u002Fcheck_composition_root_id.py",{"type":49,"value":8320}," 拦截（q067 弧长公式只显示\"=\"就是此因，加 ",{"type":43,"tag":58,"props":8322,"children":8324},{"className":8323},[],[8325],{"type":49,"value":8184},{"type":49,"value":8327}," 后重渲已修复）。",{"type":43,"tag":79,"props":8329,"children":8330},{},[8331],{"type":49,"value":8332},"(2) 必须同步渲染，切勿延迟",{"type":49,"value":8334}," — HyperFrames 渲染器按 seek GSAP 时间轴的方式",{"type":43,"tag":79,"props":8336,"children":8337},{},[8338],{"type":49,"value":8339},"确定性抓帧",{"type":49,"value":8341},"，",{"type":43,"tag":79,"props":8343,"children":8344},{},[8345,8347,8353,8354,8360],{"type":49,"value":8346},"不保证执行 ",{"type":43,"tag":58,"props":8348,"children":8350},{"className":8349},[],[8351],{"type":49,"value":8352},"setTimeout",{"type":49,"value":1697},{"type":43,"tag":58,"props":8355,"children":8357},{"className":8356},[],[8358],{"type":49,"value":8359},"requestAnimationFrame",{"type":49,"value":8361}," 等异步回调",{"type":49,"value":8363},"；因此 KaTeX 要在合成内联 ",{"type":43,"tag":58,"props":8365,"children":8367},{"className":8366},[],[8368],{"type":49,"value":5349},{"type":49,"value":343},{"type":43,"tag":79,"props":8371,"children":8372},{},[8373],{"type":49,"value":8374},"加载时同步渲染",{"type":49,"value":8376},"（此时 index.html 已在 ",{"type":43,"tag":58,"props":8378,"children":8380},{"className":8379},[],[8381],{"type":49,"value":5377},{"type":49,"value":8383}," 同步载入 katex，全局可用），",{"type":43,"tag":79,"props":8385,"children":8386},{},[8387],{"type":49,"value":8388},"不要",{"type":49,"value":8390},"把 ",{"type":43,"tag":58,"props":8392,"children":8394},{"className":8393},[],[8395],{"type":49,"value":8396},"katex.render",{"type":49,"value":8398}," 包进 ",{"type":43,"tag":58,"props":8400,"children":8402},{"className":8401},[],[8403],{"type":49,"value":8352},{"type":49,"value":8405},"\u002F轮询，也不要依赖 ",{"type":43,"tag":58,"props":8407,"children":8409},{"className":8408},[],[8410],{"type":49,"value":8411},"onload",{"type":49,"value":8413},"\u002F末尾追加脚本（追加在根 ",{"type":43,"tag":58,"props":8415,"children":8417},{"className":8416},[],[8418],{"type":49,"value":8419},"\u003C\u002Fdiv>",{"type":49,"value":8421}," 之后的 ",{"type":43,"tag":58,"props":8423,"children":8425},{"className":8424},[],[8426],{"type":49,"value":5349},{"type":49,"value":8428}," 注入时会被丢弃、根本不执行）。优先用静态 ",{"type":43,"tag":58,"props":8430,"children":8432},{"className":8431},[],[8433],{"type":49,"value":1729},{"type":49,"value":8435}," 属性 + 同步 ",{"type":43,"tag":58,"props":8437,"children":8439},{"className":8438},[],[8440],{"type":49,"value":8441},"querySelectorAll",{"type":49,"value":8443}," 渲染循环（见 step-5 Rule 4）。",{"type":43,"tag":79,"props":8445,"children":8446},{},[8447],{"type":49,"value":8448},"已知未解边缘情形",{"type":49,"value":8450},"：个别场景在 ",{"type":43,"tag":58,"props":8452,"children":8454},{"className":8453},[],[8455],{"type":49,"value":8456},"hyperframes validate",{"type":49,"value":8458},"\u002F单独打开时公式正常、但最终 MP4 里仍空白（该场景的命令式 KaTeX 未被抓帧保留，如 q062 的标注框）——遇到务必以",{"type":43,"tag":79,"props":8460,"children":8461},{},[8462],{"type":49,"value":8463},"渲染出的 MP4",{"type":49,"value":8465}," 为准复核，必要时重写该场景标记。",{"type":43,"tag":67,"props":8467,"children":8469},{"id":8468},"references-index",[8470],{"type":49,"value":8471},"References Index",{"type":43,"tag":102,"props":8473,"children":8474},{},[8475,8490],{"type":43,"tag":106,"props":8476,"children":8477},{},[8478],{"type":43,"tag":110,"props":8479,"children":8480},{},[8481,8486],{"type":43,"tag":114,"props":8482,"children":8483},{},[8484],{"type":49,"value":8485},"File",{"type":43,"tag":114,"props":8487,"children":8488},{},[8489],{"type":49,"value":8059},{"type":43,"tag":130,"props":8491,"children":8492},{},[8493,8508,8528,8547,8566,8581,8596,8611,8626,8641,8656,8671,8686,8701,8716],{"type":43,"tag":110,"props":8494,"children":8495},{},[8496,8503],{"type":43,"tag":137,"props":8497,"children":8498},{},[8499],{"type":43,"tag":765,"props":8500,"children":8501},{"href":1170},[8502],{"type":49,"value":1170},{"type":43,"tag":137,"props":8504,"children":8505},{},[8506],{"type":49,"value":8507},"Before writing any HTML",{"type":43,"tag":110,"props":8509,"children":8510},{},[8511,8518],{"type":43,"tag":137,"props":8512,"children":8513},{},[8514],{"type":43,"tag":765,"props":8515,"children":8516},{"href":1275},[8517],{"type":49,"value":1275},{"type":43,"tag":137,"props":8519,"children":8520},{},[8521,8526],{"type":43,"tag":79,"props":8522,"children":8523},{},[8524],{"type":49,"value":8525},"Few-shot example",{"type":49,"value":8527}," — geometry proof pipeline (PROBLEM → HTML compositions)",{"type":43,"tag":110,"props":8529,"children":8530},{},[8531,8538],{"type":43,"tag":137,"props":8532,"children":8533},{},[8534],{"type":43,"tag":765,"props":8535,"children":8536},{"href":8096},[8537],{"type":49,"value":8096},{"type":43,"tag":137,"props":8539,"children":8540},{},[8541,8545],{"type":43,"tag":79,"props":8542,"children":8543},{},[8544],{"type":49,"value":8525},{"type":49,"value":8546}," — interactive HTML explorer (交互式网页, 非视频): live graph + live equation via sliders, offline self-contained; full reference index.html + 6 reproduce-rules",{"type":43,"tag":110,"props":8548,"children":8549},{},[8550,8557],{"type":43,"tag":137,"props":8551,"children":8552},{},[8553],{"type":43,"tag":765,"props":8554,"children":8555},{"href":1300},[8556],{"type":49,"value":1300},{"type":43,"tag":137,"props":8558,"children":8559},{},[8560,8564],{"type":43,"tag":79,"props":8561,"children":8562},{},[8563],{"type":49,"value":8525},{"type":49,"value":8565}," — process\u002Fmotion animation (生物过程\u002F受力\u002F波动\u002F演示): split-and-move, connectors that shorten & pull (纺锤丝\u002F绳\u002F矢量), live count-up, stepped chart; 2 complete reference scenes",{"type":43,"tag":110,"props":8567,"children":8568},{},[8569,8576],{"type":43,"tag":137,"props":8570,"children":8571},{},[8572],{"type":43,"tag":765,"props":8573,"children":8574},{"href":767},[8575],{"type":49,"value":767},{"type":43,"tag":137,"props":8577,"children":8578},{},[8579],{"type":49,"value":8580},"Step 0 — when input is image_assets\u002F",{"type":43,"tag":110,"props":8582,"children":8583},{},[8584,8591],{"type":43,"tag":137,"props":8585,"children":8586},{},[8587],{"type":43,"tag":765,"props":8588,"children":8589},{"href":1182},[8590],{"type":49,"value":1182},{"type":43,"tag":137,"props":8592,"children":8593},{},[8594],{"type":49,"value":8595},"When building scene compositions (Step 5)",{"type":43,"tag":110,"props":8597,"children":8598},{},[8599,8606],{"type":43,"tag":137,"props":8600,"children":8601},{},[8602],{"type":43,"tag":765,"props":8603,"children":8604},{"href":798},[8605],{"type":49,"value":798},{"type":43,"tag":137,"props":8607,"children":8608},{},[8609],{"type":49,"value":8610},"Step 1",{"type":43,"tag":110,"props":8612,"children":8613},{},[8614,8621],{"type":43,"tag":137,"props":8615,"children":8616},{},[8617],{"type":43,"tag":765,"props":8618,"children":8619},{"href":829},[8620],{"type":49,"value":829},{"type":43,"tag":137,"props":8622,"children":8623},{},[8624],{"type":49,"value":8625},"Step 2",{"type":43,"tag":110,"props":8627,"children":8628},{},[8629,8636],{"type":43,"tag":137,"props":8630,"children":8631},{},[8632],{"type":43,"tag":765,"props":8633,"children":8634},{"href":877},[8635],{"type":49,"value":877},{"type":43,"tag":137,"props":8637,"children":8638},{},[8639],{"type":49,"value":8640},"Step 3",{"type":43,"tag":110,"props":8642,"children":8643},{},[8644,8651],{"type":43,"tag":137,"props":8645,"children":8646},{},[8647],{"type":43,"tag":765,"props":8648,"children":8649},{"href":908},[8650],{"type":49,"value":908},{"type":43,"tag":137,"props":8652,"children":8653},{},[8654],{"type":49,"value":8655},"Step 4",{"type":43,"tag":110,"props":8657,"children":8658},{},[8659,8666],{"type":43,"tag":137,"props":8660,"children":8661},{},[8662],{"type":43,"tag":765,"props":8663,"children":8664},{"href":939},[8665],{"type":49,"value":939},{"type":43,"tag":137,"props":8667,"children":8668},{},[8669],{"type":49,"value":8670},"Step 5",{"type":43,"tag":110,"props":8672,"children":8673},{},[8674,8681],{"type":43,"tag":137,"props":8675,"children":8676},{},[8677],{"type":43,"tag":765,"props":8678,"children":8679},{"href":1190},[8680],{"type":49,"value":1190},{"type":43,"tag":137,"props":8682,"children":8683},{},[8684],{"type":49,"value":8685},"生成视觉对象前必读，83 个预置组件索引",{"type":43,"tag":110,"props":8687,"children":8688},{},[8689,8696],{"type":43,"tag":137,"props":8690,"children":8691},{},[8692],{"type":43,"tag":765,"props":8693,"children":8694},{"href":1288},[8695],{"type":49,"value":1288},{"type":43,"tag":137,"props":8697,"children":8698},{},[8699],{"type":49,"value":8700},"电路原理图（电路图）绘制规范 — 电池极性、电流方向、电表接法",{"type":43,"tag":110,"props":8702,"children":8703},{},[8704,8711],{"type":43,"tag":137,"props":8705,"children":8706},{},[8707],{"type":43,"tag":765,"props":8708,"children":8709},{"href":1268},[8710],{"type":49,"value":1268},{"type":43,"tag":137,"props":8712,"children":8713},{},[8714],{"type":49,"value":8715},"几何图形构造 — 坐标计算模式、角度弧线、三角形\u002F四边形\u002F圆\u002F旋转\u002F翻折实例",{"type":43,"tag":110,"props":8717,"children":8718},{},[8719,8726],{"type":43,"tag":137,"props":8720,"children":8721},{},[8722],{"type":43,"tag":765,"props":8723,"children":8724},{"href":972},[8725],{"type":49,"value":972},{"type":43,"tag":137,"props":8727,"children":8728},{},[8729],{"type":49,"value":8730},"Step 6",{"type":43,"tag":8732,"props":8733,"children":8734},"style",{},[8735],{"type":49,"value":8736},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":8738,"total":8920},[8739,8754,8773,8789,8805,8819,8835,8848,8861,8875,8887,8902],{"slug":8740,"name":8740,"fn":8741,"description":8742,"org":8743,"tags":8744,"stars":8751,"repoUrl":8752,"updatedAt":8753},"batch","execute parallel batch operations on files","Execute batch operations on multiple files in parallel. Automatically discovers files, splits into chunks, and processes with parallel worker agents. Use `\u002Fbatch` followed by operation and file pattern.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8745,8748],{"name":8746,"slug":8747,"type":16},"Automation","automation",{"name":8749,"slug":8750,"type":16},"Concurrency","concurrency",26008,"https:\u002F\u002Fgithub.com\u002FQwenLM\u002Fqwen-code","2026-07-20T05:58:45.650067",{"slug":8755,"name":8755,"fn":8756,"description":8757,"org":8758,"tags":8759,"stars":8751,"repoUrl":8752,"updatedAt":8772},"cua-driver-rs","drive native GUI applications via MCP","Drive a native GUI app (macOS, Windows, Linux) via the cua-driver CLI (default) or MCP server — snapshot its accessibility tree, click\u002Ftype\u002Fscroll by element_index or pixel coords, verify via re-snapshot, all without bringing the target to the foreground. Use when the user asks you to operate, drive, automate, or perform a GUI task in a real application on the host.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8760,8763,8766,8769],{"name":8761,"slug":8762,"type":16},"Accessibility","accessibility",{"name":8764,"slug":8765,"type":16},"Browser Automation","browser-automation",{"name":8767,"slug":8768,"type":16},"Desktop","desktop",{"name":8770,"slug":8771,"type":16},"MCP","mcp","2026-07-16T05:59:28.687299",{"slug":8774,"name":8774,"fn":8775,"description":8776,"org":8777,"tags":8778,"stars":8751,"repoUrl":8752,"updatedAt":8788},"dataviz","design and validate data visualizations","Design guidance for charts, graphs, dashboards, maps, and data visualizations, including a local palette validator.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8779,8782,8785],{"name":8780,"slug":8781,"type":16},"Charts","charts",{"name":8783,"slug":8784,"type":16},"Dashboards","dashboards",{"name":8786,"slug":8787,"type":16},"Data Visualization","data-visualization","2026-07-16T05:59:28.31755",{"slug":8790,"name":8790,"fn":8791,"description":8792,"org":8793,"tags":8794,"stars":8751,"repoUrl":8752,"updatedAt":8804},"extension-creator","create Qwen Code extensions","Create, scaffold, customize, validate, and locally test Qwen Code extensions. Use when the user wants a new Qwen Code extension, needs help choosing an extension template, wants to add QWEN.md context, commands, skills, agents, MCP servers, settings, hooks, channels, or LSP servers, or asks how to link and test an extension locally. Invoke with `\u002Fextension-creator` followed by an extension path and optional template name.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8795,8798,8801],{"name":8796,"slug":8797,"type":16},"Coding","coding",{"name":8799,"slug":8800,"type":16},"Documentation","documentation",{"name":8802,"slug":8803,"type":16},"Plugin Development","plugin-development","2026-07-16T05:59:24.818967",{"slug":8806,"name":8806,"fn":8807,"description":8808,"org":8809,"tags":8810,"stars":8751,"repoUrl":8752,"updatedAt":8818},"loop","run scheduled or self-paced prompt loops","Create a loop that runs a prompt now and follows up either on a fixed schedule or through self-paced wakeups. Usage - \u002Floop check the build, \u002Floop 5m check the build, \u002Floop check the PR every 30m. \u002Floop list to show jobs, \u002Floop clear to cancel all.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8811,8812,8815],{"name":8746,"slug":8747,"type":16},{"name":8813,"slug":8814,"type":16},"Productivity","productivity",{"name":8816,"slug":8817,"type":16},"Scheduling","scheduling","2026-07-16T05:59:25.50027",{"slug":8820,"name":8820,"fn":8821,"description":8822,"org":8823,"tags":8824,"stars":8751,"repoUrl":8752,"updatedAt":8834},"new-app","build new applications from scratch","Workflow for creating new applications from scratch. Covers requirements gathering, tech stack selection, scaffolding, implementation, and delivery of a functional prototype.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8825,8828,8831],{"name":8826,"slug":8827,"type":16},"Engineering","engineering",{"name":8829,"slug":8830,"type":16},"Prototyping","prototyping",{"name":8832,"slug":8833,"type":16},"Web Development","web-development","2026-07-16T05:59:25.157573",{"slug":8836,"name":8836,"fn":8837,"description":8838,"org":8839,"tags":8840,"stars":8751,"repoUrl":8752,"updatedAt":8847},"qc-helper","provide Qwen Code usage and configuration support","Answer any question about Qwen Code usage, features, configuration, and troubleshooting by referencing the official user documentation. Also helps users view or modify their settings.json. Invoke with `\u002Fqc-helper` followed by a question, e.g. `\u002Fqc-helper how do I configure MCP servers?` or `\u002Fqc-helper change approval mode to yolo`.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8841,8844,8845],{"name":8842,"slug":8843,"type":16},"Configuration","configuration",{"name":8799,"slug":8800,"type":16},{"name":735,"slug":8846,"type":16},"reference","2026-07-16T05:59:29.118413",{"slug":8849,"name":8849,"fn":8850,"description":8851,"org":8852,"tags":8853,"stars":8751,"repoUrl":8752,"updatedAt":8860},"review","review code for quality and security","Review changed code for correctness, security, code quality, and performance. Use when the user asks to review code changes, a PR, or specific files. Invoke with `\u002Freview`, `\u002Freview \u003Cpr-number>`, `\u002Freview \u003Cfile-path>`, `\u002Freview \u003Cpr-number> --comment` to post inline comments on the PR, or `\u002Freview --fix` to apply the findings to your working tree. Add `--effort low|medium|high` to trade depth for speed (defaults to high for PRs, medium for local changes).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8854,8857],{"name":8855,"slug":8856,"type":16},"Code Review","code-review",{"name":8858,"slug":8859,"type":16},"Security","security","2026-08-05T05:37:32.013531",{"slug":8862,"name":8862,"fn":8863,"description":8864,"org":8865,"tags":8866,"stars":8751,"repoUrl":8752,"updatedAt":8874},"simplify","clean up and simplify code changes","Review recent code changes for reuse, code quality, and efficiency, then directly apply straightforward cleanup improvements. Use when the user wants a post-implementation cleanup pass, pre-PR polish, or asks to simplify\u002Frefine recent changes. Invoke with `\u002Fsimplify` or `\u002Fsimplify \u003Cfocus>`.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8867,8870,8871],{"name":8868,"slug":8869,"type":16},"Code Analysis","code-analysis",{"name":8826,"slug":8827,"type":16},{"name":8872,"slug":8873,"type":16},"Performance","performance","2026-07-20T05:58:46.653811",{"slug":8876,"name":8876,"fn":8877,"description":8878,"org":8879,"tags":8880,"stars":8751,"repoUrl":8752,"updatedAt":8886},"stuck","diagnose and debug stuck Qwen Code sessions","Diagnose frozen, stuck, or slow Qwen Code sessions on this machine. Scans for problematic processes, high CPU\u002Fmemory usage, hung subprocesses, and debug logs. Use \u002Fstuck or \u002Fstuck \u003CPID> to focus on a specific process.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8881,8884,8885],{"name":8882,"slug":8883,"type":16},"Debugging","debugging",{"name":8826,"slug":8827,"type":16},{"name":8872,"slug":8873,"type":16},"2026-07-16T05:59:25.838629",{"slug":8888,"name":8888,"fn":8889,"description":8890,"org":8891,"tags":8892,"stars":8899,"repoUrl":8900,"updatedAt":8901},"open-computer-use","configure Open Computer Use MCP server","Platform-neutral guidance for using Open Computer Use, the open-source Computer Use MCP server and CLI for macOS, Linux, and Windows. Use when an agent needs to install, verify, troubleshoot, configure, or operate Open Computer Use through its native CLI, stdio MCP server, or direct Computer Use tool calls.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8893,8894,8897,8898],{"name":8746,"slug":8747,"type":16},{"name":8895,"slug":8896,"type":16},"CLI","cli",{"name":8767,"slug":8768,"type":16},{"name":8770,"slug":8771,"type":16},176,"https:\u002F\u002Fgithub.com\u002FQwenLM\u002Fopen-computer-use","2026-07-16T05:59:22.010839",{"slug":8903,"name":8903,"fn":8904,"description":8905,"org":8906,"tags":8907,"stars":8917,"repoUrl":8918,"updatedAt":8919},"auto-pr","automate pull request submission and review","Automated PR submission assistant, including code review, documentation generation, and PR creation",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8908,8909,8910,8911,8914],{"name":8746,"slug":8747,"type":16},{"name":8855,"slug":8856,"type":16},{"name":8799,"slug":8800,"type":16},{"name":8912,"slug":8913,"type":16},"GitHub","github",{"name":8915,"slug":8916,"type":16},"Pull Requests","pull-requests",136,"https:\u002F\u002Fgithub.com\u002FQwenLM\u002Fqwen-code-examples","2026-07-16T06:00:01.890524",26,{"items":8922,"total":3665},[8923,8942,8959,8966,8979],{"slug":8924,"name":8924,"fn":8925,"description":8926,"org":8927,"tags":8928,"stars":26,"repoUrl":27,"updatedAt":8941},"qwen-mm-plugins-blender","build and edit 3D scenes in Blender","Use whenever a task involves building or editing a 3D scene or asset in Blender — modeling, characters\u002Fpeople, architecture\u002Finteriors, terrain\u002Flandscapes, props, materials, lighting, or rendering. Covers discovering installed add-ons, using generators, importing and REFINING ready-made assets, and matching the result to the spec. Requires a running Blender instance with the blender-mcp addon (see Prerequisite).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8929,8932,8935,8938],{"name":8930,"slug":8931,"type":16},"3D","3d",{"name":8933,"slug":8934,"type":16},"Animation","animation",{"name":8936,"slug":8937,"type":16},"Creative","creative",{"name":8939,"slug":8940,"type":16},"Design","design","2026-08-05T05:58:30.425245",{"slug":8943,"name":8943,"fn":8944,"description":8945,"org":8946,"tags":8947,"stars":26,"repoUrl":27,"updatedAt":8958},"qwen-mm-plugins-core","analyze and visualize multimodal files","Vision-Language MCP tools for reading\u002Fvisualizing any file (images, videos, documents, code, data, 3D models, etc.) and calling VL APIs (caption, ASR, search, etc.).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8948,8951,8954,8955],{"name":8949,"slug":8950,"type":16},"Data Analysis","data-analysis",{"name":8952,"slug":8953,"type":16},"LLM","llm",{"name":8770,"slug":8771,"type":16},{"name":8956,"slug":8957,"type":16},"Multimodal","multimodal","2026-08-05T05:58:39.78188",{"slug":4,"name":4,"fn":5,"description":6,"org":8960,"tags":8961,"stars":26,"repoUrl":27,"updatedAt":28},{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8962,8963,8964,8965],{"name":14,"slug":15,"type":16},{"name":21,"slug":22,"type":16},{"name":18,"slug":19,"type":16},{"name":24,"slug":25,"type":16},{"slug":8967,"name":8967,"fn":8968,"description":8969,"org":8970,"tags":8971,"stars":26,"repoUrl":27,"updatedAt":8978},"qwen-mm-plugins-freecad","model parts and assemblies in FreeCAD","Use whenever a task involves parametric CAD in FreeCAD — modeling parts and assemblies, editing object properties, technical drawings, importing\u002Fexporting STEP\u002FSTL\u002FOBJ\u002FDXF, PDF\u002FExcel reports from a model, or finite-element (FEM\u002FCalculiX) analysis. Requires a running FreeCAD instance with the FreeCADMCP addon (see Prerequisite).",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8972,8973,8976,8977],{"name":8930,"slug":8931,"type":16},{"name":8974,"slug":8975,"type":16},"CAD","cad",{"name":8939,"slug":8940,"type":16},{"name":8826,"slug":8827,"type":16},"2026-08-05T05:58:40.567406",{"slug":8980,"name":8980,"fn":8981,"description":8982,"org":8983,"tags":8984,"stars":26,"repoUrl":27,"updatedAt":8990},"qwen-mm-plugins-video-memory","analyze long form video content","Triggered for long videos (30+ minutes), whether a single file or a directory of multiple videos. Vision-language MCP tools designed for efficient reading and semantic analysis of long videos (30+ minutes), supporting memory construction and semantic search.",{"slug":8,"name":9,"logoUrl":10,"githubOrg":11},[8985,8988,8989],{"name":8986,"slug":8987,"type":16},"Analysis","analysis",{"name":8956,"slug":8957,"type":16},{"name":24,"slug":25,"type":16},"2026-08-05T05:58:28.007509"]