@shadowclone/cli 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/_.._/web/client/index.html +13 -12
  2. package/dist/shadowclone.js +524 -503
  3. package/dist/web/index-25g5ahxa.css +1 -0
  4. package/dist/web/index-s4mjwgw0.js +134 -0
  5. package/package.json +3 -9
  6. package/plugins/shadowclone/.claude-plugin/plugin.json +1 -1
  7. package/plugins/shadowclone/.codex-plugin/plugin.json +1 -1
  8. package/plugins/shadowclone/plugin.json +1 -1
  9. package/skills/choose-by-consequence/SKILL.md +66 -0
  10. package/skills/design-deep-modules/SKILL.md +32 -15
  11. package/skills/diagnose-before-editing/SKILL.md +38 -17
  12. package/skills/plan-with-review-page/SKILL.md +73 -0
  13. package/skills/plan-with-review-page/assets/review-page.html +160 -0
  14. package/skills/plan-with-review-page/scripts/validate-page.mjs +158 -0
  15. package/skills/research-primary-sources/SKILL.md +32 -14
  16. package/skills/resolve-conflicts-by-intent/SKILL.md +34 -15
  17. package/skills/scope-confirmed-changes/SKILL.md +44 -14
  18. package/skills/shadowclone-work/SKILL.md +5 -2
  19. package/skills/tests-that-catch-bugs/SKILL.md +73 -0
  20. package/skills/typescript-type-safety/SKILL.md +32 -16
  21. package/skills/verify-and-review/SKILL.md +40 -16
  22. package/skills/verify-review-findings/SKILL.md +59 -0
  23. package/skills/write-plain-english/SKILL.md +58 -0
  24. package/skills/write-plain-english/scripts/check-ste.mjs +200 -0
  25. package/dist/web/index-ntyv9x8a.css +0 -1
  26. package/dist/web/index-twq4tm3n.js +0 -135
  27. package/skills/prove-regression-tests/SKILL.md +0 -33
  28. package/skills/testing-first/SKILL.md +0 -35
  29. package/skills/testing-risk-based/SKILL.md +0 -34
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ import { readFile } from "node:fs/promises";
3
+
4
+ const maximumWords = 25;
5
+ const dash = /[\u2013\u2014]/;
6
+ const passive =
7
+ /\b(?:is|are|was|were|be|been|being)\s+(?:not\s+)?(?:\w+ly\s+)?(\w{3,}(?:ed|en))\b/i;
8
+ const notParticiples = new Set(["often", "even", "seven", "eleven", "token", "listen", "golden"]);
9
+ const phrasalVerbs = [
10
+ "carry out",
11
+ "come up with",
12
+ "end up",
13
+ "figure out",
14
+ "find out",
15
+ "get rid of",
16
+ "go over",
17
+ "kick off",
18
+ "look into",
19
+ "make use of",
20
+ "point out",
21
+ "set up",
22
+ "take care of",
23
+ "turn out",
24
+ ];
25
+ const vagueWords = [
26
+ "basically",
27
+ "etc",
28
+ "leverage",
29
+ "numerous",
30
+ "robust",
31
+ "seamless",
32
+ "seamlessly",
33
+ "simply",
34
+ "utilize",
35
+ "various",
36
+ ];
37
+
38
+ function proseLines(text) {
39
+ const lines = text.split("\n");
40
+ const prose = [];
41
+ let fence = null;
42
+ let frontmatter = lines[0] === "---";
43
+
44
+ for (const [index, line] of lines.entries()) {
45
+ const number = index + 1;
46
+
47
+ if (frontmatter) {
48
+ if (index > 0 && line === "---") {
49
+ frontmatter = false;
50
+ }
51
+
52
+ const description = /^description:\s*(["']?)(.*)\1\s*$/.exec(line);
53
+
54
+ if (description) {
55
+ prose.push({ number, text: description[2].replace(/''/g, "'") });
56
+ }
57
+
58
+ continue;
59
+ }
60
+
61
+ const marker = /^\s*(`{3,}|~{3,})/.exec(line)?.[1];
62
+
63
+ if (marker) {
64
+ fence = fence === null ? marker : marker.startsWith(fence) ? null : fence;
65
+
66
+ continue;
67
+ }
68
+
69
+ if (fence !== null || /^\s*(?:#|\||$)/.test(line)) {
70
+ continue;
71
+ }
72
+
73
+ prose.push({
74
+ number,
75
+ text: line
76
+ .replace(/`[^`]*`/g, "Code")
77
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
78
+ .replace(/^\s*(?:[-*]|\d+\.)\s+/, "")
79
+ .replace(/\*\*([^*]+):\*\*/g, "$1:")
80
+ .replace(/[*_]/g, ""),
81
+ });
82
+ }
83
+
84
+ return prose;
85
+ }
86
+
87
+ function sentencesOf(text) {
88
+ return text
89
+ .split(/(?<=[.!?])\s+(?=["'(A-Z0-9])/)
90
+ .map((sentence) => sentence.trim())
91
+ .filter((sentence) => sentence.length > 0);
92
+ }
93
+
94
+ function wordCount(sentence) {
95
+ return sentence.split(/\s+/).filter((word) => /[A-Za-z0-9]/.test(word)).length;
96
+ }
97
+
98
+ function checkText(text) {
99
+ const findings = [];
100
+
101
+ for (const line of proseLines(text)) {
102
+ if (dash.test(line.text)) {
103
+ findings.push({
104
+ line: line.number,
105
+ level: "error",
106
+ rule: "dash",
107
+ message: "replace the em or en dash with a comma, a period, parentheses, or the word to",
108
+ });
109
+ }
110
+
111
+ for (const sentence of sentencesOf(line.text)) {
112
+ const words = wordCount(sentence);
113
+ const unquoted = sentence.replace(/"[^"]*"/g, " ");
114
+ const lower = ` ${unquoted.toLowerCase().replace(/[^a-z0-9' ]+/g, " ")} `;
115
+ const passiveMatch = passive.exec(unquoted);
116
+ const passiveWord = passiveMatch?.[1]?.toLowerCase();
117
+
118
+ if (words > maximumWords) {
119
+ findings.push({
120
+ line: line.number,
121
+ level: "error",
122
+ rule: "long-sentence",
123
+ message: `${words} words, split it to ${maximumWords} or fewer: "${sentence.slice(0, 60)}"`,
124
+ });
125
+ }
126
+
127
+ if (passiveMatch && passiveWord && !notParticiples.has(passiveWord)) {
128
+ findings.push({
129
+ line: line.number,
130
+ level: "warning",
131
+ rule: "passive",
132
+ message: `"${passiveMatch[0]}" may be passive, name who does the action`,
133
+ });
134
+ }
135
+
136
+ for (const verb of phrasalVerbs.filter((entry) => lower.includes(` ${entry} `))) {
137
+ findings.push({
138
+ line: line.number,
139
+ level: "warning",
140
+ rule: "phrasal-verb",
141
+ message: `"${verb}" is a phrasal verb, use a single verb`,
142
+ });
143
+ }
144
+
145
+ for (const word of vagueWords.filter((entry) => lower.includes(` ${entry} `))) {
146
+ findings.push({
147
+ line: line.number,
148
+ level: "warning",
149
+ rule: "vague-word",
150
+ message: `"${word}" is vague, say exactly what you mean`,
151
+ });
152
+ }
153
+ }
154
+ }
155
+
156
+ return findings;
157
+ }
158
+
159
+ async function main(files) {
160
+ if (files.length === 0) {
161
+ console.error("Usage: check-ste.mjs <file>...");
162
+
163
+ return 2;
164
+ }
165
+
166
+ let errors = 0;
167
+ let warnings = 0;
168
+
169
+ for (const file of files) {
170
+ let text;
171
+
172
+ try {
173
+ text = await readFile(file, "utf8");
174
+ } catch (error) {
175
+ console.error(`${file}: cannot read the file (${error.code ?? error.message})`);
176
+
177
+ return 2;
178
+ }
179
+
180
+ for (const finding of checkText(text)) {
181
+ console.log(`${file}:${finding.line}: ${finding.level} ${finding.rule} ${finding.message}`);
182
+
183
+ if (finding.level === "error") {
184
+ errors += 1;
185
+ } else {
186
+ warnings += 1;
187
+ }
188
+ }
189
+ }
190
+
191
+ const plural = (count, word) => `${count} ${word}${count === 1 ? "" : "s"}`;
192
+
193
+ console.log(
194
+ `check-ste: ${plural(errors, "error")}, ${plural(warnings, "warning")} in ${plural(files.length, "file")}`,
195
+ );
196
+
197
+ return errors > 0 ? 1 : 0;
198
+ }
199
+
200
+ process.exitCode = await main(process.argv.slice(2));
@@ -1 +0,0 @@
1
- :root{--buncss-light: ;--buncss-dark:initial;color-scheme:dark;color:#e5e4ed;--muted:#929bb3;--accent:#b6a2ee;--line:#303246;--surface:#171923;background:#090a10;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:15px}*{box-sizing:border-box}body{background:radial-gradient(at 50% 45%,#291d3c5c,#0000 65%);min-height:100vh;margin:0;padding:28px 24px}button,input,select,textarea{font:inherit}button,select{cursor:pointer}button{border:0}button:disabled{cursor:wait;opacity:.5}button,a,input,textarea,select{outline-offset:5px}:focus-visible{outline:2px solid var(--accent)}button,a{-webkit-tap-highlight-color:transparent}[hidden]{display:none!important}.frame{overflow:clip;background:#10121b;border:1px solid #373043;border-radius:20px;max-width:1040px;margin:0 auto;box-shadow:0 24px 100px #0008,0 0 100px #9b71d208}.masthead{border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items: center;padding:18px 32px}.brand{display:inline-flex;color:inherit;text-decoration:none;align-items: center;gap:9px;font-weight:650}.brand-mark{filter:drop-shadow(0 0 8px #c0a0ff33);width:28px;height:28px}.local-badge{letter-spacing:.12em;color:var(--muted);font-size:10px}.local-badge span{display:inline-block;background:#8dd0bd;border-radius:50%;width:6px;height:6px;margin-right:6px}main{padding:32px 36px 0}.eyebrow{letter-spacing:.22em;color:var(--accent);margin:0 0 12px;font-size:10px;font-weight:650}h1,h2{font-family:Georgia,Times New Roman,serif;font-weight:400}h1{letter-spacing:-.035em;margin:0 0 12px;font-size:clamp(30px,4vw,44px)}h2{letter-spacing:-.02em;margin:0;font-size:28px}.subtitle{color:var(--muted);margin:0;line-height:1.6}.toolbar{display:flex;align-items: end;gap:14px;margin-top:24px}.scope-field{display:grid;letter-spacing:.04em;color:var(--muted);gap:7px;font-size:10px}.scope-field select{appearance:none;background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSIxNCIgdmlld0JveD0iMCAwIDE0IDE0IiBmaWxsPSJub25lIj4KICA8cGF0aCBkPSJtMyA1IDQgNCA0LTQiIHN0cm9rZT0iI2I3YWVjOSIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K);background-position:right 14px center;background-repeat:no-repeat;min-width:200px;padding-right:40px}input,select,textarea{color:#e5e4ed;border:1px solid var(--line);background-color:#12141f;border-radius:7px;padding:10px 12px;font-size:13px}textarea{resize:vertical;width:100%;line-height:1.6}input::placeholder,textarea::placeholder{color:#747c93}.search-field{flex:1}.search-field input{width:100%}.quiet-button{color:#bec3d4;background:0 0;border-radius:7px;padding:11px 12px;font-size:13px}.quiet-button:hover{color:#fff;background:#222533}.primary-button{color:#181323;background:linear-gradient(145deg,#c3acf5,#aa8dde);border:1px solid #dcc9ff4d;border-radius:8px;padding:14px 22px;transition:box-shadow .2s,transform .2s,background .2s;font-size:13px;font-weight:650;box-shadow:0 3px 20px #b997ec1f,inset 0 1px #f1e6ff30}.primary-button:hover{background:linear-gradient(145deg,#d3c0ff,#b79ae9);transform:translateY(-1px);box-shadow:0 0 26px #ba95f23b}.primary-button span{margin-left:16px}.scope-note{color:var(--muted);min-height:17px;margin:0;font-size:12px;line-height:1.6}.notice{color:#e1d4ab;background:#27231b;border:1px solid #796c44;border-radius:8px;margin-top:20px;padding:12px 16px;font-size:13px;line-height:1.6}.notice.success{color:#abdacb;background:#132722;border-color:#33594d}.visually-hidden{position:absolute;overflow:hidden;clip-path:inset(50%);width:1px;height:1px}.footer{display:flex;border-top:1px solid var(--line);position:sticky;z-index:4;backdrop-filter:blur(18px);background:#171824ed;justify-content:space-between;align-items: center;gap:20px;padding:20px 32px;bottom:0;box-shadow:0 -12px 30px #0b0c1326}.footer strong{font-size:14px;font-weight:600}.footer p{color:var(--muted);margin:8px 0 0;font-size:12px}.footer-actions{display:flex;align-items: center;gap:12px}@media (width<=720px){body{padding:12px}main{padding:26px 18px 0}.masthead,.footer{padding:18px}.toolbar{flex-wrap:wrap}.scope-field,.scope-field select{width:100%}.footer{align-items: flex-start}.footer-actions{flex-direction:column-reverse;align-items: flex-end}.local-badge{font-size:9px}}@media (prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important}}.workspace{display:grid;grid-template-columns:minmax(0,1fr)290px;align-items: start;gap:32px;margin:28px 0}.constellation{position:relative;overflow:hidden;background:radial-gradient(circle at 50% 100%,#29203a99,#11131c 68%);border:1px solid #2c2a3a;border-radius:16px;min-width:0;height:clamp(560px,72vh,820px)}.constellation-controls{position:absolute;z-index:3;display:flex;gap:6px;top:12px;right:12px}.constellation-controls .quiet-button{background:#151723e8;padding:7px 10px;font-size:11px}.constellation-status{position:absolute;z-index:4;color:#d4d7e3;text-align:center;background:#151723;border:1px solid #423a50;border-radius:12px;width:min(280px,85%);padding:18px;top:40%;left:50%;transform:translate(-50%,-50%)}.constellation-status p{margin:0 0 12px}.constellation-map,.constellation-list{position:absolute;width:100%;height:100%;inset:0}.constellation-map{z-index:1;touch-action:none}.map-link{fill:none;stroke:#76668f;stroke-width:1;opacity:.62}.map-link.related{stroke:#8b75ae;stroke-dasharray:3 7;opacity:.38}.map-node{color:#b1a7c6;cursor:pointer;outline:none}.map-node rect,.map-node circle{fill:#171824;stroke:#645474;stroke-width:1;transition:fill .16s,stroke .16s,opacity .16s}.map-node text{fill:#d4d7e3;paint-order:stroke;stroke:#10121b;stroke-width:5px;stroke-linejoin:round;font:15px Inter,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif}.map-node text.map-node-count{fill:#9c90b5;letter-spacing:0;font-size:12px;font-weight:400}.map-node-hub text{fill:#cebaf5;letter-spacing:.04em;font-size:14px;font-weight:650}.map-node-hub rect{fill:#211b2f;stroke:#a88cda}.map-node-root circle{fill:#332647;stroke:#c1a5ee;stroke-width:1.5}.map-node-root image{pointer-events:none}.map-node:hover rect,.map-node:hover circle,.map-node:focus rect,.map-node:focus circle,.map-node.focused rect{fill:#382c4c;stroke:#d0b8ff;stroke-width:2}.map-node.equipped rect{fill:#544268;stroke:#ceb8ff;filter:drop-shadow(0 0 10px #b78cf766)}.map-node.search-miss{opacity:.16}.map-node-icon{display:grid;color:#c7b5ef;place-items: center;width:18px;height:18px}.map-node-icon svg{width:17px;height:17px}.constellation-list{z-index:2;display:none;overflow:auto;background:#11131cf5;padding:64px 20px 50px}.list-view .constellation-map,.list-view .tree-hint{display:none}.list-view .constellation-list{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));align-content: start;gap:20px}.skill-list-group h3{color:#c9b2f1;letter-spacing:.08em;text-transform:uppercase;margin:0 0 9px;font-size:12px}.skill-list-item{display:block;color:#cfd3df;text-align:left;background:0 0;border-left:1px solid #423a50;width:100%;padding:8px 10px}.skill-list-item:hover,.skill-list-item:focus,.skill-list-item.equipped{color:#f2eaff;background:#241e30;border-left-color:#b999ed}.tree-hint{position:absolute;z-index:2;color:var(--muted);text-align:center;pointer-events:none;margin:0;font-size:11px;bottom:12px;left:0;right:0}@media (width<=900px){.workspace{grid-template-columns:minmax(0,1fr)270px;gap:20px}}@media (width<=720px){.workspace{grid-template-columns:1fr}.constellation{height:min(680px,70vh)}.constellation-map,.tree-hint{display:none}.constellation-list{display:grid;grid-template-columns:1fr;align-content: start;gap:20px}.constellation-controls{display:none}}.detail{position:sticky;background:linear-gradient(150deg,#1d1b2a,#151720 55%);border:1px solid #53446799;border-radius:14px;padding:24px;top:24px;box-shadow:0 12px 40px #0003,inset 0 1px #d2b9ff0a}.detail h2{font-size:26px;line-height:1.2}.detail-description{color:#a5afc5;display:-webkit-box;-webkit-line-clamp:3;overflow:hidden;-webkit-box-orient:vertical;margin:14px 0 24px;font-size:13px;line-height:1.7}.behavior{border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:21px 0}.behavior p:last-child{color:#c3c5d0;white-space:pre-line;margin:0;font-size:13px;line-height:1.7}.skill-status{color:#8fcaba;margin:18px 0;font-size:11px;line-height:1.6}.detail .primary-button{width:100%}.detail .editor{margin-top:20px}.detail summary{color:#bdb5d2;cursor:pointer;font-size:12px}.detail textarea{min-height:200px;margin-top:12px;font:11px/1.65 ui-monospace,monospace}.detail .quiet-button{width:100%;margin-top:8px}.owner-note{color:var(--muted);font-size:11px;line-height:1.6}.identity{display:flex;border-top:1px solid #292b3d;justify-content:space-between;align-items: center;gap:24px;padding:26px 0}.identity h2{font-size:26px}.identity p:last-child{color:var(--muted);max-width:580px;margin-bottom:0;font-size:12px;line-height:1.7}.traits{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:7px;max-width:300px}.trait{color:#c2b5e7;white-space:nowrap;background:linear-gradient(135deg,#30253f,#211c2e);border:1px solid #3a324a;border-radius:30px;padding:7px 11px;font-size:11px;box-shadow:inset 0 1px #d2b9ff0a}.requirements{border:1px solid var(--line);background:#141824;border-radius:10px;margin:0 0 22px;padding:18px}.requirements h3{color:#b6c4d4;margin:0 0 10px;font-size:12px}.requirements p{color:var(--muted);margin:7px 0 0;font-size:12px;line-height:1.6}dialog{color:#e5e4ed;background:#151720;border:1px solid #414258;border-radius:18px;width:min(780px,100vw - 40px);max-height:88vh;padding:28px;box-shadow:0 35px 120px #0008}dialog::backdrop{backdrop-filter:blur(4px);background:#06070be3}.dialog-heading{display:flex;justify-content:space-between;align-items: flex-start;gap:20px;margin-bottom:25px}.dialog-heading .quiet-button{margin-top:-8px}.dialog-actions{display:flex;justify-content:flex-end;gap:12px;margin-top:24px}#review-content{overflow:auto;max-height:52vh}.file-change{border:1px solid var(--line);border-radius:8px;margin:12px 0;padding:14px}.file-change summary{cursor:pointer;color:#c7b8e8;overflow-wrap:anywhere;font-size:12px}.file-change h4{text-transform:uppercase;letter-spacing:.12em;color:var(--muted);margin-bottom:8px;font-size:10px}.file-change pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#0e1119;border-radius:5px;padding:14px;font:11px/1.6 ui-monospace,monospace}.file-change p,.review-message{color:#a3adc2;font-size:12px;line-height:1.7}.review-warning{color:#dac895;font-size:12px;line-height:1.7}form label{display:grid;color:#bbc3d7;gap:9px;margin-top:20px;font-size:12px}@media (width<=720px){.detail{position:static}.identity{display:block}.traits{justify-content:flex-start;max-width:none;margin-top:18px}dialog{padding:20px}}.bot-dialog{overflow-y:auto;width:min(48rem,100vw - 3rem);max-height:calc(100vh - 3rem)}.bot-dialog .field{display:grid;gap:.4rem;margin:1rem 0}.bot-dialog details{margin:.7rem 0}.bot-dialog pre{white-space:pre-wrap;overflow-wrap:anywhere}.bot-dialog .bot-consent{display:block;margin:1.3rem 0}.bot-dialog .bot-actions{display:block;margin:1rem 0}.constellation{isolation:isolate}.constellation:before{content:"";position:absolute;pointer-events:none;z-index:-1;filter:blur(15px);background:radial-gradient(at 53% 72%,#be85f13d 0%,#0000 42%),radial-gradient(at 28% 52%,#8060c32b 0%,#0000 48%),radial-gradient(at 74% 41%,#59699b1f 0%,#0000 42%),radial-gradient(at 53% 57%,#7c47822e 0%,#0000 65%);inset:30px -35px 5px}.tree-root img{filter:drop-shadow(0 0 7px #bca1f38c);width:31px;height:31px}.tree-root span:before{content:"";position:absolute;border:1px solid #b19bdf26;border-radius:50%;inset:-9px;box-shadow:0 0 35px #be85f12e}.tree-root span{box-shadow:0 0 22px #c7a1ff40,0 0 75px #a776de26}.skill-node:hover .node-gem,.skill-node.active .node-gem{box-shadow:0 0 18px #c7a8fa33,0 0 55px #9773d92b}.skill-node.equipped .node-gem{box-shadow:0 0 16px #c7a8fa38,0 0 40px #a776de38,inset 0 0 12px #c7a8fa0d}.skill-node.equipped .node-icon{filter:drop-shadow(0 0 4px #d6bfff66)}.skill-node.equipped:after{box-shadow:0 0 10px #ceadff99}.scope-meta{display:flex;justify-content:space-between;align-items: start;gap:16px;margin-top:12px}.library-note{text-align:right;color:var(--muted);max-width:270px;font-size:11px;line-height:1.6}.library-note summary{cursor:pointer;color:#a799c3}.library-note p{margin:8px 0}.library-note code{display:block;color:#c8b4f0;font-size:10px}#describe{color:#b2a1d1;margin-top:14px;padding:11px 12px}#model-description:empty{display:none}@media (width<=720px){.scope-meta{display:block}.library-note{text-align:left;margin-top:8px}}.starfield{position:absolute;pointer-events:none;z-index:-1;overflow:hidden;width:100%;height:100%;inset:0}.star{fill:#e6d6ff;opacity:0;transform-box:fill-box;transform-origin:center;animation:9s linear infinite star-drift}.star-rays{stroke:#eadbff;stroke-width:.55;opacity:.75}.star-phase-0{animation-duration:8.3s;animation-delay:-1.2s}.star-phase-1{animation-duration:11.1s;animation-delay:-5.8s}.star-phase-2{animation-duration:9.7s;animation-delay:-8.3s}.star-phase-3{animation-duration:12.4s;animation-delay:-3.4s}.star-phase-4{animation-duration:7.8s;animation-delay:-6.2s}.star-phase-5{animation-duration:10.6s;animation-delay:-2.7s}.star-phase-6{animation-duration:13.2s;animation-delay:-10.4s}.starfield.paused .star{animation-play-state:paused}@keyframes star-drift{0%,to{opacity:0;transform:translate(-8px,5px)scale(.65)}20%{opacity:.45}36%,59%{opacity:.95}45%,72%{opacity:.25}85%{opacity:0;transform:translate(12px,-9px)scale(1)}}@media (prefers-reduced-motion:reduce){.star{animation:none;opacity:.65;transform:none}}.build-panel{position:sticky;display:grid;overflow:auto;scrollbar-width:thin;scrollbar-color:#4d3d65 transparent;gap:16px;max-height:calc(100dvh - 140px);margin:-3px;padding:3px;top:20px}.build-panel .identity{display:block;background:linear-gradient(145deg,#221c30,#14151f 80%);border:1px solid #44364f;border-radius:14px;padding:22px}.build-panel .identity h2{font-size:25px}.build-panel .identity p:last-child{font-size:12px;line-height:1.65}.build-panel .traits{justify-content:flex-start;gap:6px;margin-top:14px}.build-panel .trait{padding:6px 9px;font-size:10px}.build-panel .detail{position:static}.build-panel #model-description{margin-top:16px;margin-bottom:0}@media (width<=720px){.build-panel{position:static;overflow:visible;max-height:none}}