@jlcpcb/cli 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @jlcpcb/cli
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Remove the 50 mm symbol tolerance and report blocking pin validation errors with explicit scope in JSON, text, and HTML; preserve failures for intentional templates and incomplete source metadata.
8
+ - Bundle corrected browser pad/drill rendering, complete symbol curves and fills, and visible property placement for off-origin symbols.
9
+ - Apply corrected footprint geometry and conservative standard-package substitution during installation and regeneration.
10
+ - Fail footprint validation on manufacturing differences or unverified geometry, and check batch fixture identities against live part/package metadata.
11
+
12
+ - Preserve destination, force, and 3D options throughout LCSC and community interactive installation.
13
+ - Keep search constraints and filtered results when toggling filters or returning from component details.
14
+ - Handle Escape once, discard abandoned setup work, and return from completed setup and installation to component details.
15
+ - Exit unsuccessfully for regeneration failures and report missing requested validation references as failures.
16
+
3
17
  ## 0.4.0
4
18
 
5
19
  ### Minor Changes
package/README.md CHANGED
@@ -72,10 +72,8 @@ jlc info C8734 --json
72
72
  | Key | Action |
73
73
  |-----|--------|
74
74
  | `Enter` | Install component |
75
- | `S` | Open symbol file |
76
- | `F` | Open footprint file |
77
- | `M` | Open 3D model |
78
- | `D` | Open datasheet URL |
75
+ | `D` | Delete installed component |
76
+ | `O` | Open datasheet URL |
79
77
  | `R` | Regenerate symbol/footprint |
80
78
  | `Esc` | Go back |
81
79
 
@@ -103,12 +101,16 @@ jlc install C8734 --force # Regenerate if exists
103
101
  | `-p, --project <path>` | Install to project-local library |
104
102
  | `--with-3d` | Include 3D model (STEP) |
105
103
  | `-f, --force` | Force reinstall/regenerate |
104
+ | `-y, --yes` | Install directly without the TUI |
105
+ | `--json` | Install directly and emit JSON |
106
+
107
+ Destination, force, and 3D options apply throughout interactive selection and confirmation as well as direct installation. A project-local install checks and updates that project's library rather than a global copy.
106
108
 
107
109
  **Installation Result:**
108
110
 
109
111
  After installation, you'll see:
110
112
  - **Symbol reference**: e.g., `JLC-MCP-ICs:STM32F103C8T6`
111
- - **Footprint reference**: e.g., `Package_QFP:LQFP-48_7x7mm_P0.5mm`
113
+ - **Footprint reference**: a KiCad standard passive reference or a generated `JLC-MCP:...` reference
112
114
  - Validation info (pin/pad counts, power pins detected)
113
115
 
114
116
  ### `jlc library`
@@ -140,6 +142,22 @@ Shows all installed components with:
140
142
 
141
143
  Navigate with `↑` `↓` and press `Enter` to view component details.
142
144
 
145
+ ### Regeneration and validation exit status
146
+
147
+ `jlc library regenerate --project ./my-kicad-project` reports each component failure and exits nonzero if any regeneration fails.
148
+
149
+ `jlc validate C8734 --json` also exits nonzero when a requested symbol or footprint reference is unavailable. `--symbol-only` and `--footprint-only` disable the other comparison; a missing requested reference is never treated as a successful comparison.
150
+
151
+ Footprint validation checks copper boundaries, layers, plating, slot dimensions/orientation/offset, mechanical holes, and vias. Unknown manufacturing metadata fails as unverified rather than passing on pad count alone. Batch fixtures also verify the live manufacturer part number and package identity. The footprint limits are 0.05 mm for position and 0.02 mm for full dimensions.
152
+
153
+ Symbol validation checks pin identity, names, relative connection layout, direction, and declared electrical type. It ignores only global translation, with a maximum 0.05 mm position tolerance and 1° direction tolerance. Missing/duplicate identities, unsupported metadata, and meaningful differences are blocking errors—not warning-only passes.
154
+
155
+ JSON and HTML reports explicitly identify this as **pin validation**: body graphics, text, pin lengths, and pin styles are not compared. Standardized passive symbols can intentionally differ from their source and still fail this comparison. The live fixture catalog also retains failures for empty RP2040 pin electrical metadata and duplicate Mini-USB pin identities; fixture expectations never bypass CLI failures.
156
+
157
+ Generate a side-by-side report with `jlc validate C42417612 --symbol-only --html validation.html`. Browser previews preserve capsules, custom copper, offsets, arcs, fills, and visible labels, but use browser fonts rather than KiCad's stroke fonts.
158
+
159
+ Generation preserves source geometry or returns an error for ambiguous drills and unsupported cutout semantics. Standard substitution is limited to compatible unpolarized two-terminal R/C/L packages; it intentionally uses KiCad's nominal land pattern. Existing installations need explicit regeneration, for example `jlc install C8734 --force`, to receive these corrections.
160
+
143
161
  ## Keyboard Navigation
144
162
 
145
163
  Global keyboard shortcuts across all screens:
@@ -452,76 +452,77 @@
452
452
  </div>
453
453
 
454
454
  <script>
455
- function EJ(Z){let $=[],J=0;while(J<Z.length){let K=Z[J];if(/\s/.test(K)){J++;continue}if(K==="("||K===")"){$.push(K),J++;continue}if(K==='"'){let V="";J++;while(J<Z.length)if(Z[J]==="\\"&&J+1<Z.length)V+=Z[J+1],J+=2;else if(Z[J]==='"'){J++;break}else V+=Z[J],J++;$.push(`"${V}"`);continue}let U="";while(J<Z.length&&!/[\s()]/.test(Z[J]))U+=Z[J],J++;if(U)$.push(U)}return $}function ZJ(Z){if(Z.length===0)return{expr:[],remaining:[]};let $=Z[0];if($==="("){let K=[],U=Z.slice(1);while(U.length>0&&U[0]!==")"){let{expr:V,remaining:j}=ZJ(U);K.push(V),U=j}if(U[0]===")")U=U.slice(1);return{expr:K,remaining:U}}if($===")")return{expr:[],remaining:Z.slice(1)};let J=$;if(J.startsWith('"')&&J.endsWith('"'))J=J.slice(1,-1);return{expr:J,remaining:Z.slice(1)}}function d(Z){let $=EJ(Z),{expr:J}=ZJ($);return J}function B(Z){return Array.isArray(Z)}function X(Z){return typeof Z==="string"}function v(Z){if(B(Z)&&Z.length>0&&X(Z[0]))return Z[0];return}function z(Z,$){if(!B(Z))return;for(let J of Z)if(B(J)&&v(J)===$)return J;return}function I(Z,$){if(!B(Z))return[];let J=[];for(let K of Z)if(B(K)&&v(K)===$)J.push(K);return J}function y(Z,$){let J=z(Z,$);if(J&&J.length>=2&&X(J[1]))return J[1];return}function b(Z,$){let J=y(Z,$);if(J!==void 0){let K=parseFloat(J);if(!isNaN(K))return K}return}function _(Z,$){let J=z(Z,$);if(J&&J.length>=3){let K=parseFloat(X(J[1])?J[1]:""),U=parseFloat(X(J[2])?J[2]:"");if(!isNaN(K)&&!isNaN(U))return{x:K,y:U}}return}function c(Z,$){let J=z(Z,$);if(J&&J.length>=3){let K=parseFloat(X(J[1])?J[1]:""),U=parseFloat(X(J[2])?J[2]:"");if(!isNaN(K)&&!isNaN(U)){let V={x:K,y:U};if(J.length>=4&&X(J[3])){let j=parseFloat(J[3]);if(!isNaN(j))V.rotation=j}return V}}return}function o(Z){let $=z(Z,"size");if($&&$.length>=3){let J=parseFloat(X($[1])?$[1]:""),K=parseFloat(X($[2])?$[2]:"");if(!isNaN(J)&&!isNaN(K))return{width:J,height:K}}return}function P(Z){let $=z(Z,"stroke");if(!$)return;let J=b($,"width")??0.254,K=y($,"type")??"default";return{width:J,type:K}}function f(Z){let $=z(Z,"fill");if(!$)return;let J=y($,"type");if(J)return J;if($.length>=2&&X($[1]))return $[1];return}function u(Z){let $=z(Z,"pts");if(!$)return[];let J=[];for(let K of $)if(B(K)&&v(K)==="xy"&&K.length>=3){let U=parseFloat(X(K[1])?K[1]:""),V=parseFloat(X(K[2])?K[2]:"");if(!isNaN(U)&&!isNaN(V))J.push({x:U,y:V})}return J}function R(Z){let $=y(Z,"layer");if($)return[$];let J=z(Z,"layers");if(!J)return[];let K=[];for(let U=1;U<J.length;U++){let V=J[U];if(X(V))K.push(V)}return K}var A={background:"#FFFFF8",body:"#840000",bodyFill:"#FFFFC4",pin:"#840000",pinText:"#008484",text:"#000000"},w={background:"#000000",fCu:"#840000",bCu:"#008400",in1Cu:"#C2C200",in2Cu:"#C200C2",fSilkS:"#008484",bSilkS:"#840084",fMask:"#840084",bMask:"#848400",fPaste:"#840000",bPaste:"#00C2C2",fFab:"#848484",bFab:"#000084",fCrtYd:"#C2C2C2",bCrtYd:"#848484",edgeCuts:"#C2C200",dwgsUser:"#C2C2C2",cmtsUser:"#848484",margin:"#C200C2",padFront:"#840000",padBack:"#008400",padThruHole:"#C2C200",drill:"#848484"},S=10;function g(Z){return Z.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function a(Z){if(!Z||Z.trim()==="")return T("No symbol data");try{let $=d(Z);if(B($)&&v($)==="kicad_symbol_lib"){let D=I($,"symbol");if(D.length===0)return T("No symbol found in library");$=D[0]}if(!B($)||v($)!=="symbol")return T("Invalid symbol format");let J=[],K={minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0},U=I($,"symbol");for(let D of U){for(let H of I(D,"rectangle")){let W=XJ(H,K);if(W)J.push(W)}for(let H of I(D,"polyline")){let W=wJ(H,K);if(W)J.push(W)}for(let H of I(D,"circle")){let W=MJ(H,K);if(W)J.push(W)}for(let H of I(D,"arc")){let W=IJ(H,K);if(W)J.push(W)}for(let H of I(D,"pin")){let W=YJ(H,K);if(W)J.push(W)}}let V=5;if(!isFinite(K.minX))K.minX=-20,K.maxX=20,K.minY=-20,K.maxY=20;let j=(K.maxX-K.minX+V*2)*S,Q=(K.maxY-K.minY+V*2)*S;return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${`${(K.minX-V)*S} ${(-K.maxY-V)*S} ${j} ${Q}`}" width="100%" height="100%" style="background-color: ${A.background}">
456
- <g transform="scale(${S}, ${-S})">
457
- ${J.join(`
455
+ function Bt(t){let e=[],n=0;while(n<t.length){let i=t[n];if(/\s/.test(i)){n++;continue}if(i==="("||i===")"){e.push(i),n++;continue}if(i==='"'){let s="";n++;while(n<t.length)if(t[n]==="\\"&&n+1<t.length)s+=t[n+1],n+=2;else if(t[n]==='"'){n++;break}else s+=t[n],n++;e.push(`"${s}"`);continue}let o="";while(n<t.length&&!/[\s()]/.test(t[n]))o+=t[n],n++;if(o)e.push(o)}return e}function gt(t){if(t.length===0)return{expr:[],remaining:[]};let e=t[0];if(e==="("){let i=[],o=t.slice(1);while(o.length>0&&o[0]!==")"){let{expr:s,remaining:a}=gt(o);i.push(s),o=a}if(o[0]===")")o=o.slice(1);return{expr:i,remaining:o}}if(e===")")return{expr:[],remaining:t.slice(1)};let n=e;if(n.startsWith('"')&&n.endsWith('"'))n=n.slice(1,-1);return{expr:n,remaining:t.slice(1)}}function nt(t){let e=Bt(t),{expr:n}=gt(e);return n}function k(t){return Array.isArray(t)}function b(t){return typeof t==="string"}function F(t){if(k(t)&&t.length>0&&b(t[0]))return t[0];return}function y(t,e){if(!k(t))return;for(let n of t)if(k(n)&&F(n)===e)return n;return}function L(t,e){if(!k(t))return[];let n=[];for(let i of t)if(k(i)&&F(i)===e)n.push(i);return n}function T(t,e){let n=y(t,e);if(n&&n.length>=2&&b(n[1]))return n[1];return}function Y(t,e){let n=T(t,e);if(n!==void 0){let i=parseFloat(n);if(!isNaN(i))return i}return}function C(t,e){let n=y(t,e);if(n&&n.length>=3){let i=parseFloat(b(n[1])?n[1]:""),o=parseFloat(b(n[2])?n[2]:"");if(!isNaN(i)&&!isNaN(o))return{x:i,y:o}}return}function V(t,e){let n=y(t,e);if(n&&n.length>=3){let i=parseFloat(b(n[1])?n[1]:""),o=parseFloat(b(n[2])?n[2]:"");if(!isNaN(i)&&!isNaN(o)){let s={x:i,y:o};if(n.length>=4&&b(n[3])){let a=parseFloat(n[3]);if(!isNaN(a))s.rotation=a}return s}}return}function et(t){let e=y(t,"size");if(e&&e.length>=3){let n=parseFloat(b(e[1])?e[1]:""),i=parseFloat(b(e[2])?e[2]:"");if(!isNaN(n)&&!isNaN(i))return{width:n,height:i}}return}function j(t){let e=y(t,"stroke");if(!e)return;let n=Y(e,"width")??0.254,i=T(e,"type")??"default";return{width:n,type:i}}function it(t){let e=y(t,"fill");if(!e)return;let n=T(e,"type");if(n)return n;if(e.length>=2&&b(e[1]))return e[1];return}function K(t){let e=y(t,"pts");if(!e)return[];let n=[];for(let i of e)if(k(i)&&F(i)==="xy"&&i.length>=3){let o=parseFloat(b(i[1])?i[1]:""),s=parseFloat(b(i[2])?i[2]:"");if(!isNaN(o)&&!isNaN(s))n.push({x:o,y:s})}return n}function D(t){let e=T(t,"layer");if(e)return[e];let n=y(t,"layers");if(!n)return[];let i=[];for(let o=1;o<n.length;o++){let s=n[o];if(b(s))i.push(s)}return i}var B={background:"#FFFFF8",body:"#840000",bodyFill:"#FFFFC4",pin:"#840000",pinText:"#008484",text:"#000000"},x={background:"#000000",fCu:"#840000",bCu:"#008400",in1Cu:"#C2C200",in2Cu:"#C200C2",fSilkS:"#008484",bSilkS:"#840084",fMask:"#840084",bMask:"#848400",fPaste:"#840000",bPaste:"#00C2C2",fFab:"#848484",bFab:"#000084",fCrtYd:"#C2C2C2",bCrtYd:"#848484",edgeCuts:"#C2C200",dwgsUser:"#C2C2C2",cmtsUser:"#848484",margin:"#C200C2",padFront:"#840000",padBack:"#008400",padThruHole:"#C2C200",drill:"#848484"},U=10;function q(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function ot(t){if(!t||t.trim()==="")return X("No symbol data");try{let e=nt(t);if(k(e)&&F(e)==="kicad_symbol_lib"){let c=L(e,"symbol");if(c.length===0)return X("No symbol found in library");e=c[0]}if(!k(e)||F(e)!=="symbol")return X("Invalid symbol format");let n=[],i={minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0},o=L(e,"symbol"),s=y(e,"pin_names"),a=y(e,"pin_numbers");for(let c of L(e,"property"))n.push(O(c,i,!0));for(let c of o){for(let l of L(c,"rectangle")){let d=Ft(l,i);if(d)n.push(d)}for(let l of L(c,"polyline")){let d=Pt(l,i);if(d)n.push(d)}for(let l of L(c,"circle")){let d=Tt(l,i);if(d)n.push(d)}for(let l of L(c,"arc")){let d=It(l,i);if(d)n.push(d)}for(let l of L(c,"text"))n.push(O(l,i,!0));for(let l of L(c,"pin")){let d=Ht(l,i,s,a);if(d)n.push(d)}}let f=5;if(!isFinite(i.minX))i.minX=-20,i.maxX=20,i.minY=-20,i.maxY=20;let u=(i.maxX-i.minX+f*2)*U,r=(i.maxY-i.minY+f*2)*U;return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${`${(i.minX-f)*U} ${(-i.maxY-f)*U} ${u} ${r}`}" width="100%" height="100%" style="background-color: ${B.background}">
456
+ <g transform="scale(${U}, ${-U})">
457
+ ${n.join(`
458
458
  `)}
459
459
  </g>
460
- </svg>`}catch($){return T(`Parse error: ${$}`)}}function XJ(Z,$){let J=_(Z,"start"),K=_(Z,"end");if(!J||!K)return"";let U=P(Z),j=f(Z)==="background"?A.bodyFill:"none";E($,J.x,J.y),E($,K.x,K.y);let Q=Math.min(J.x,K.x),G=Math.min(J.y,K.y),D=Math.abs(K.x-J.x),H=Math.abs(K.y-J.y);return`<rect x="${Q}" y="${G}" width="${D}" height="${H}" fill="${j}" stroke="${A.body}" stroke-width="${U?.width??0.254}"/>`}function wJ(Z,$){let J=u(Z);if(J.length<2)return"";let K=P(Z),V=f(Z)==="background"?A.bodyFill:"none";return`<polyline points="${J.map((Q)=>{return E($,Q.x,Q.y),`${Q.x},${Q.y}`}).join(" ")}" fill="${V}" stroke="${A.body}" stroke-width="${K?.width??0.254}" stroke-linecap="round" stroke-linejoin="round"/>`}function MJ(Z,$){let J=_(Z,"center"),K=b(Z,"radius");if(!J||K===void 0)return"";let U=P(Z),j=f(Z)==="background"?A.bodyFill:"none";return E($,J.x-K,J.y-K),E($,J.x+K,J.y+K),`<circle cx="${J.x}" cy="${J.y}" r="${K}" fill="${j}" stroke="${A.body}" stroke-width="${U?.width??0.254}"/>`}function IJ(Z,$){let J=_(Z,"start"),K=_(Z,"mid"),U=_(Z,"end");if(!J||!K||!U)return"";let V=P(Z);return E($,J.x,J.y),E($,K.x,K.y),E($,U.x,U.y),`<path d="${jJ(J,K,U)}" fill="none" stroke="${A.body}" stroke-width="${V?.width??0.254}" stroke-linecap="round"/>`}function jJ(Z,$,J){let{x:K,y:U}=Z,V=$.x,j=$.y,Q=J.x,G=J.y,D=2*(K*(j-G)+V*(G-U)+Q*(U-j));if(Math.abs(D)<0.0001)return`M ${Z.x} ${Z.y} L ${J.x} ${J.y}`;let H=((K*K+U*U)*(j-G)+(V*V+j*j)*(G-U)+(Q*Q+G*G)*(U-j))/D,W=((K*K+U*U)*(Q-V)+(V*V+j*j)*(K-Q)+(Q*Q+G*G)*(V-K))/D,q=Math.sqrt((K-H)**2+(U-W)**2),M=(V-K)*(G-U)-(j-U)*(Q-K)>0?0:1,N=0;return`M ${Z.x} ${Z.y} A ${q} ${q} 0 ${N} ${M} ${J.x} ${J.y}`}function YJ(Z,$){let J=c(Z,"at"),K=b(Z,"length")??2.54;if(!J)return"";let U=z(Z,"name"),V=z(Z,"number"),j=U&&U.length>=2&&X(U[1])?U[1]:"",Q=V&&V.length>=2&&X(V[1])?V[1]:"",G=J.rotation??0,D=G*Math.PI/180,H=J.x+K*Math.cos(D),W=J.y+K*Math.sin(D);E($,J.x,J.y),E($,H,W);let q=[];if(q.push(`<line x1="${J.x}" y1="${J.y}" x2="${H}" y2="${W}" stroke="${A.pin}" stroke-width="0.254"/>`),q.push(`<circle cx="${J.x}" cy="${J.y}" r="0.3" fill="${A.pin}"/>`),j&&j!=="~"){let M=H,N=W,k="start",F="middle";if(G===0)M=H+0.5,k="start";else if(G===180)M=H-0.5,k="end";else if(G===90)N=W+0.5,F="hanging",k="middle";else if(G===270)N=W-0.5,F="alphabetic",k="middle";q.push(`<text x="${M}" y="${N}" fill="${A.pinText}" font-size="1" font-family="sans-serif" text-anchor="${k}" dominant-baseline="${F}" transform="scale(1,-1) translate(0,${-2*N})">${g(j)}</text>`)}if(Q){let Y=(J.x+H)/2,M=(J.y+W)/2,N=G===0||G===180?0.8:0;q.push(`<text x="${Y}" y="${M+N}" fill="${A.pinText}" font-size="0.8" font-family="sans-serif" text-anchor="middle" dominant-baseline="middle" transform="scale(1,-1) translate(0,${-2*(M+N)})">${g(Q)}</text>`)}return q.join(`
461
- `)}function s(Z){if(!Z||Z.trim()==="")return T("No footprint data");try{let $=d(Z);if(!B($)||v($)!=="footprint")return T("Invalid footprint format");let J=[],K={minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0};for(let G of I($,"pad")){let D=NJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_line")){let D=$J(G,K);if(D)J.push(D)}for(let G of I($,"fp_circle")){let D=KJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_arc")){let D=UJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_rect")){let D=DJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_poly")){let D=VJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_line")){let D=$J(G,K);if(D)J.push(D)}for(let G of I($,"gr_circle")){let D=KJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_arc")){let D=UJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_rect")){let D=DJ(G,K);if(D)J.push(D)}for(let G of I($,"gr_poly")){let D=VJ(G,K);if(D)J.push(D)}for(let G of I($,"fp_text")){let D=zJ(G,K);if(D)J.push(D)}let U=1;if(!isFinite(K.minX))K.minX=-5,K.maxX=5,K.minY=-5,K.maxY=5;let V=K.maxX-K.minX+U*2,j=K.maxY-K.minY+U*2;return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${`${K.minX-U} ${K.minY-U} ${V} ${j}`}" width="100%" height="100%" style="background-color: ${w.background}">
460
+ </svg>`}catch(e){return X(`Preview error: ${e}`)}}function Q(t){let e=it(t);return e==="background"?B.bodyFill:e==="outline"||e==="solid"?B.body:"none"}function Ft(t,e){let n=C(t,"start"),i=C(t,"end");if(!n||!i)return"";let o=j(t)?.width||0.254;return v(e,n.x,n.y,o/2),v(e,i.x,i.y,o/2),`<rect x="${Math.min(n.x,i.x)}" y="${Math.min(n.y,i.y)}" width="${Math.abs(i.x-n.x)}" height="${Math.abs(i.y-n.y)}" fill="${Q(t)}" stroke="${B.body}" stroke-width="${o}" stroke-linejoin="round"/>`}function Pt(t,e){let n=K(t);if(n.length<2)return"";let i=j(t)?.width||0.254;for(let o of n)v(e,o.x,o.y,i/2);return`<polyline points="${n.map((o)=>`${o.x},${o.y}`).join(" ")}" fill="${Q(t)}" stroke="${B.body}" stroke-width="${i}" stroke-linecap="round" stroke-linejoin="round"/>`}function Tt(t,e){let n=C(t,"center"),i=Y(t,"radius");if(!n||i===void 0)return"";let o=j(t)?.width||0.254;return v(e,n.x,n.y,i+o/2),`<circle cx="${n.x}" cy="${n.y}" r="${i}" fill="${Q(t)}" stroke="${B.body}" stroke-width="${o}"/>`}function It(t,e){let n=C(t,"start"),i=C(t,"mid"),o=C(t,"end");if(!n||!i||!o)return"";let s=j(t)?.width||0.254;return`<path d="${yt(n,i,o,e,s)}" fill="${Q(t)}" stroke="${B.body}" stroke-width="${s}" stroke-linecap="round"/>`}function A(t,e){let n=-(e.rotation??0)*Math.PI/180;return{x:e.x+t.x*Math.cos(n)-t.y*Math.sin(n),y:e.y+t.x*Math.sin(n)+t.y*Math.cos(n)}}function yt(t,e,n,i,o){let s=e.x-t.x,a=e.y-t.y,f=n.x-t.x,u=n.y-t.y,r=2*(s*u-a*f);for(let h of[t,e,n])v(i,h.x,h.y,o/2);if(Math.abs(r)<=Number.EPSILON*Math.max(s*s+a*a,f*f+u*u)*16)return`M ${t.x} ${t.y} L ${e.x} ${e.y} L ${n.x} ${n.y}`;let m=s*s+a*a,c=f*f+u*u,l=(m*u-c*a)/r,d=(s*c-f*m)/r,g={x:t.x+l,y:t.y+d},w=Math.hypot(l,d),p=Math.PI*2,S=(h)=>(h%p+p)%p,M=Math.atan2(-d,-l),I=Math.atan2(n.y-g.y,n.x-g.x),P=r>0,E=S(P?I-M:M-I);for(let h=0;h<p;h+=Math.PI/2)if(S(P?h-M:M-h)<=E+0.000000000001)v(i,g.x+w*Math.cos(h),g.y+w*Math.sin(h),o/2);return`M ${t.x} ${t.y} A ${w} ${w} 0 ${E>Math.PI?1:0} ${P?1:0} ${n.x} ${n.y}`}function z(t){if(!t)return!1;let e=y(t,"hide");return t.includes("hide")||!!e&&(e.length===1||e[1]==="yes")}function Ht(t,e,n,i){if(z(t))return"";let o=V(t,"at");if(!o)return"";let s=Y(t,"length")??2.54,a=((o.rotation??0)%360+360)%360,f=a*Math.PI/180,u={x:Math.cos(f),y:Math.sin(f)},r={x:o.x+s*u.x,y:o.y+s*u.y},m=b(t[2])?t[2]:"line";if(!["line","inverted","clock","inverted_clock"].includes(m))throw Error(`Unsupported pin style: ${m}`);let c=m==="inverted"||m==="inverted_clock",l=m==="clock"||m==="inverted_clock",d=0.3,g=c?{x:r.x-2*d*u.x,y:r.y-2*d*u.y}:r;v(e,o.x,o.y,0.127),v(e,r.x,r.y,0.127);let w=[`<line x1="${o.x}" y1="${o.y}" x2="${g.x}" y2="${g.y}" stroke="${B.pin}" stroke-width="0.254"/>`];if(c){let E={x:r.x-d*u.x,y:r.y-d*u.y};v(e,E.x,E.y,d+0.127),w.push(`<circle cx="${E.x}" cy="${E.y}" r="${d}" fill="none" stroke="${B.pin}" stroke-width="0.254"/>`)}if(l){let E=[{x:r.x-u.y*0.6,y:r.y+u.x*0.6},{x:r.x+u.x*0.6,y:r.y+u.y*0.6},{x:r.x+u.y*0.6,y:r.y-u.x*0.6}];for(let h of E)v(e,h.x,h.y,0.127);w.push(`<polyline points="${E.map((h)=>`${h.x},${h.y}`).join(" ")}" fill="none" stroke="${B.pin}" stroke-width="0.254" stroke-linejoin="round"/>`)}let p=y(t,"name"),S=y(t,"number"),M=n?Y(n,"offset")??0.508:0.508,I=M===0?{x:(o.x+r.x)/2-u.y*0.8,y:(o.y+r.y)/2+u.x*0.8}:{x:r.x+u.x*M,y:r.y+u.y*M},P=a===90||a===270?90:0;if(p&&p[1]!=="~"&&!z(n)){let E=M===0?[]:[["justify",a===180||a===270?"right":"left"]],h=y(p,"effects")??["effects",["font",["size","1.27","1.27"]]];w.push(O(["text",p[1],["at",`${I.x}`,`${I.y}`,`${P}`],[...h,...E]],e,!0,B.pinText))}if(S&&!z(i)){let E=M===0?-1:1,h={x:(o.x+r.x)/2-u.y*0.8*E,y:(o.y+r.y)/2+u.x*0.8*E};w.push(O(["text",S[1],["at",`${h.x}`,`${h.y}`,`${P}`],y(S,"effects")??["effects",["font",["size","1.27","1.27"]]]],e,!0,B.pinText))}return w.join(`
461
+ `)}function rt(t){if(!t||t.trim()==="")return X("No footprint data");try{let e=nt(t);if(!k(e)||F(e)!=="footprint")return X("Invalid footprint format");let n=[],i={minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0};for(let r of L(e,"pad")){let m=Nt(r,i);if(m)n.push(m)}let o=new Map;for(let r of L(e,"property"))if(b(r[1])&&b(r[2]))o.set(r[1].toUpperCase(),r[2]);for(let r of e){if(!k(r))continue;let m=F(r)??"";if(m==="fp_text"||m==="property")n.push(O(r,i,!1,void 0,o));else if(m.startsWith("fp_")||m.startsWith("gr_"))n.push(vt(r,i));else if(m==="zone")n.push(Yt(r,i))}let s=1;if(!isFinite(i.minX))i.minX=-5,i.maxX=5,i.minY=-5,i.maxY=5;let a=i.maxX-i.minX+s*2,f=i.maxY-i.minY+s*2;return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${`${i.minX-s} ${i.minY-s} ${a} ${f}`}" width="100%" height="100%" style="background-color: ${x.background}">
462
462
  <g>
463
- ${J.join(`
463
+ ${n.join(`
464
464
  `)}
465
465
  </g>
466
- </svg>`}catch($){return T(`Parse error: ${$}`)}}function C(Z){if(Z.includes("F.Cu"))return w.fCu;if(Z.includes("B.Cu"))return w.bCu;if(/In\d+\.Cu/.test(Z))return w.in1Cu;if(Z.includes("F.SilkS"))return w.fSilkS;if(Z.includes("B.SilkS"))return w.bSilkS;if(Z.includes("F.Mask"))return w.fMask;if(Z.includes("B.Mask"))return w.bMask;if(Z.includes("F.Paste"))return w.fPaste;if(Z.includes("B.Paste"))return w.bPaste;if(Z.includes("F.Fab"))return w.fFab;if(Z.includes("B.Fab"))return w.bFab;if(Z.includes("F.CrtYd"))return w.fCrtYd;if(Z.includes("B.CrtYd"))return w.bCrtYd;if(Z.includes("Edge.Cuts"))return w.edgeCuts;if(Z.includes("Dwgs.User"))return w.dwgsUser;if(Z.includes("Cmts.User"))return w.cmtsUser;if(Z.includes("Margin"))return w.margin;return w.fSilkS}function NJ(Z,$){if(!B(Z)||Z.length<4)return"";let J=X(Z[1])?Z[1]:"",K=X(Z[2])?Z[2]:"",U=X(Z[3])?Z[3]:"",V=c(Z,"at"),j=o(Z);if(!V||!j)return"";E($,V.x-j.width/2,V.y-j.height/2),E($,V.x+j.width/2,V.y+j.height/2);let Q=[],G=R(Z),D;if(K==="thru_hole"||K==="np_thru_hole")D=w.padThruHole;else if(G.some((q)=>q.includes("B.")))D=w.padBack;else D=w.padFront;let H=V.rotation??0,W=H!==0?` transform="rotate(${H}, ${V.x}, ${V.y})"`:"";if(U==="circle"){let q=Math.min(j.width,j.height)/2;Q.push(`<circle cx="${V.x}" cy="${V.y}" r="${q}" fill="${D}"${W}/>`)}else if(U==="oval"){let q=j.width/2,Y=j.height/2;Q.push(`<ellipse cx="${V.x}" cy="${V.y}" rx="${q}" ry="${Y}" fill="${D}"${W}/>`)}else if(U==="roundrect"){let q=b(Z,"roundrect_rratio")??0.25,Y=Math.min(j.width,j.height)*q/2,M=V.x-j.width/2,N=V.y-j.height/2;Q.push(`<rect x="${M}" y="${N}" width="${j.width}" height="${j.height}" rx="${Y}" fill="${D}"${W}/>`)}else if(U==="custom"){let q=z(Z,"primitives");if(q)for(let Y of I(q,"gr_poly")){let M=u(Y);if(M.length>=3){let N=M.map((k)=>{let F=V.x+k.x,m=V.y+k.y;return E($,F,m),`${F},${m}`}).join(" ");Q.push(`<polygon points="${N}" fill="${D}"${W}/>`)}}if(Q.length===0){let Y=V.x-j.width/2,M=V.y-j.height/2;Q.push(`<rect x="${Y}" y="${M}" width="${j.width}" height="${j.height}" fill="${D}"${W}/>`)}}else{let q=V.x-j.width/2,Y=V.y-j.height/2;Q.push(`<rect x="${q}" y="${Y}" width="${j.width}" height="${j.height}" fill="${D}"${W}/>`)}if(K==="thru_hole"||K==="np_thru_hole"){let q=z(Z,"drill");if(q&&q.length>=2)if(X(q[1])&&q[1]==="oval"&&q.length>=4){let M=parseFloat(X(q[2])?q[2]:"0"),N=parseFloat(X(q[3])?q[3]:"0");Q.push(`<ellipse cx="${V.x}" cy="${V.y}" rx="${M/2}" ry="${N/2}" fill="${w.drill}"${W}/>`)}else{let M=parseFloat(X(q[1])?q[1]:"0");Q.push(`<circle cx="${V.x}" cy="${V.y}" r="${M/2}" fill="${w.drill}"/>`)}}if(J){let q=Math.min(j.width,j.height)*0.5;Q.push(`<text x="${V.x}" y="${V.y}" fill="#FFFFFF" font-size="${q}" font-family="sans-serif" text-anchor="middle" dominant-baseline="central">${g(J)}</text>`)}return Q.join(`
467
- `)}function $J(Z,$){let J=_(Z,"start"),K=_(Z,"end");if(!J||!K)return"";let U=P(Z),j=R(Z)[0]??"F.SilkS";if(j.includes("CrtYd"))return"";let Q=C(j);return E($,J.x,J.y),E($,K.x,K.y),`<line x1="${J.x}" y1="${J.y}" x2="${K.x}" y2="${K.y}" stroke="${Q}" stroke-width="${U?.width??0.15}" stroke-linecap="round"/>`}function KJ(Z,$){let J=_(Z,"center"),K=_(Z,"end");if(!J||!K)return"";let U=Math.sqrt((K.x-J.x)**2+(K.y-J.y)**2),V=P(Z),Q=R(Z)[0]??"F.SilkS";if(Q.includes("CrtYd"))return"";let G=C(Q);return E($,J.x-U,J.y-U),E($,J.x+U,J.y+U),`<circle cx="${J.x}" cy="${J.y}" r="${U}" fill="none" stroke="${G}" stroke-width="${V?.width??0.15}"/>`}function UJ(Z,$){let J=_(Z,"start"),K=_(Z,"mid"),U=_(Z,"end");if(!J||!K||!U)return"";let V=P(Z),Q=R(Z)[0]??"F.SilkS";if(Q.includes("CrtYd"))return"";let G=C(Q);return E($,J.x,J.y),E($,K.x,K.y),E($,U.x,U.y),`<path d="${jJ(J,K,U)}" fill="none" stroke="${G}" stroke-width="${V?.width??0.15}" stroke-linecap="round"/>`}function VJ(Z,$){let J=u(Z);if(J.length<3)return"";let U=R(Z)[0]??"F.Cu";if(U.includes("CrtYd"))return"";let V=C(U),j=P(Z),Q=f(Z),G=Q==="solid"||Q==="yes"?V:"none",D=J.map((q)=>{return E($,q.x,q.y),`${q.x},${q.y}`}).join(" "),H=j?.width??0,W=H>0?` stroke="${V}" stroke-width="${H}"`:"";return`<polygon points="${D}" fill="${G}"${W}/>`}function DJ(Z,$){let J=_(Z,"start"),K=_(Z,"end");if(!J||!K)return"";let V=R(Z)[0]??"F.SilkS";if(V.includes("CrtYd"))return"";let j=C(V),Q=P(Z),D=f(Z)==="solid"?j:"none";E($,J.x,J.y),E($,K.x,K.y);let H=Math.min(J.x,K.x),W=Math.min(J.y,K.y),q=Math.abs(K.x-J.x),Y=Math.abs(K.y-J.y);return`<rect x="${H}" y="${W}" width="${q}" height="${Y}" fill="${D}" stroke="${j}" stroke-width="${Q?.width??0.15}"/>`}function zJ(Z,$){if(!B(Z)||Z.length<3)return"";let J=X(Z[1])?Z[1]:"",K=X(Z[2])?Z[2]:"";if(J==="reference"||J==="value")return"";let U=c(Z,"at");if(!U)return"";let j=R(Z)[0]??"F.SilkS",Q=C(j),G=z(Z,"effects"),D=1,H="middle";if(G){let N=z(G,"font");if(N){let F=o(N);if(F)D=F.height}let k=z(G,"justify");if(k)for(let F=1;F<k.length;F++){let m=X(k[F])?k[F]:"";if(m==="left")H="start";else if(m==="right")H="end"}}let W=K.length*D*0.6,q=D;if(H==="start")E($,U.x,U.y-q/2),E($,U.x+W,U.y+q/2);else if(H==="end")E($,U.x-W,U.y-q/2),E($,U.x,U.y+q/2);else E($,U.x-W/2,U.y-q/2),E($,U.x+W/2,U.y+q/2);let Y=U.rotation??0,M=Y!==0?` transform="rotate(${Y}, ${U.x}, ${U.y})"`:"";return`<text x="${U.x}" y="${U.y}" dy="0.35em" fill="${Q}" font-size="${D}" font-family="sans-serif" text-anchor="${H}"${M}>${g(K)}</text>`}function E(Z,$,J){Z.minX=Math.min(Z.minX,$),Z.maxX=Math.max(Z.maxX,$),Z.minY=Math.min(Z.minY,J),Z.maxY=Math.max(Z.maxY,J)}function T(Z){return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="100%" height="100%">
466
+ </svg>`}catch(e){return X(`Preview error: ${e}`)}}function xt(t){if(t.includes("F.Cu"))return x.fCu;if(t.includes("B.Cu"))return x.bCu;if(/In\d+\.Cu/.test(t))return x.in1Cu;if(t.includes("F.SilkS"))return x.fSilkS;if(t.includes("B.SilkS"))return x.bSilkS;if(t.includes("F.Mask"))return x.fMask;if(t.includes("B.Mask"))return x.bMask;if(t.includes("F.Paste"))return x.fPaste;if(t.includes("B.Paste"))return x.bPaste;if(t.includes("F.Fab"))return x.fFab;if(t.includes("B.Fab"))return x.bFab;if(t.includes("F.CrtYd"))return x.fCrtYd;if(t.includes("B.CrtYd"))return x.bCrtYd;if(t.includes("Edge.Cuts"))return x.edgeCuts;if(t.includes("Dwgs.User"))return x.dwgsUser;if(t.includes("Cmts.User"))return x.cmtsUser;if(t.includes("Margin"))return x.margin;return x.fSilkS}function Nt(t,e){let n=b(t[1])?t[1]:"",i=b(t[2])?t[2]:"",o=b(t[3])?t[3]:"",s=V(t,"at"),a=et(t);if(!s||!a)throw Error(`Invalid pad geometry: ${n}`);if(!["rect","roundrect","circle","oval","custom"].includes(o)||y(t,"chamfer")||y(t,"rect_delta"))throw Error(`Unsupported pad geometry: ${o}`);let f=D(t),u=i==="thru_hole"||i==="np_thru_hole"?x.padThruHole:f.includes("B.Cu")&&!f.includes("F.Cu")?x.padBack:x.padFront,r=y(t,"drill"),m=r?C(r,"offset")??{x:0,y:0}:{x:0,y:0},c={...A(m,s),rotation:s.rotation},l={minX:1/0,maxX:-1/0,minY:1/0,maxY:-1/0},d=[],g=o==="custom"?T(y(t,"options")??[],"anchor")??"rect":o;if(!["rect","roundrect","circle","oval"].includes(g))throw Error(`Unsupported custom anchor: ${g}`);let w=g==="oval"?Math.min(a.width,a.height)/2:g==="roundrect"?Math.min(a.width,a.height)*(Y(t,"roundrect_rratio")??0.25):0;if(g==="circle"){let p=a.width/2;v(l,c.x,c.y,p),d.push(`<circle cx="${c.x}" cy="${c.y}" r="${p}" fill="${u}"/>`)}else d.push(ht(a.width,a.height,w,c,u,l));if(o==="custom"){let p=y(t,"primitives");for(let S of p??[])if(k(S))d.push(vt(S,l,c,u))}if(v(e,l.minX,l.minY),v(e,l.maxX,l.maxY),r){let p=r[1]==="oval",S=Number(p?r[2]:r[1]),M=Number(p?r[3]:r[1]);if(!(S>0&&M>0))throw Error(`Invalid drill geometry: ${n}`);d.push(ht(S,M,Math.min(S,M)/2,s,x.drill,e))}if(n){let p=(o==="custom"?Math.min(l.maxX-l.minX,l.maxY-l.minY):Math.min(a.width,a.height))*0.5,S=n.length*p*0.7;v(e,c.x-S/2,c.y-p*0.75),v(e,c.x+S/2,c.y+p*0.75),d.push(`<text x="${c.x}" y="${c.y}" fill="#FFFFFF" font-size="${p}" textLength="${S}" lengthAdjust="spacingAndGlyphs" font-family="sans-serif" text-anchor="middle" dominant-baseline="central">${q(n)}</text>`)}return`<g data-pad="${q(n)}">${d.join(`
467
+ `)}</g>`}function ht(t,e,n,i,o,s){let a=Math.min(Math.max(n,0),Math.min(t,e)/2),f=(i.rotation??0)*Math.PI/180,u=Math.abs(Math.cos(f)),r=Math.abs(Math.sin(f)),m=u*(t/2-a)+r*(e/2-a)+a,c=r*(t/2-a)+u*(e/2-a)+a;return v(s,i.x-m,i.y-c),v(s,i.x+m,i.y+c),`<rect x="${-t/2}" y="${-e/2}" width="${t}" height="${e}" rx="${a}" fill="${o}" transform="translate(${i.x} ${i.y}) rotate(${-(i.rotation??0)})"/>`}function vt(t,e,n={x:0,y:0},i){let o=F(t)??"",s=o.replace(/^(fp|gr)_/,""),a=i??xt(D(t)[0]??"F.SilkS"),f=j(t)?.width??Y(t,"width")??(i?0:0.15),u=it(t),r=u==="solid"||u==="yes"?a:"none",m=`stroke="${a}" stroke-width="${f}" stroke-linecap="round" stroke-linejoin="round"`;if(s==="line"||s==="arc"||s==="rect"){let c=C(t,"start"),l=C(t,"end");if(!c||!l)throw Error(`Invalid ${o} geometry`);let d=A(c,n),g=A(l,n);if(s==="arc"){let w=C(t,"mid");if(!w)throw Error(`Unsupported ${o} without midpoint`);return`<path d="${yt(d,A(w,n),g,e,f)}" fill="none" ${m}/>`}if(s==="rect"){let w=[c,{x:l.x,y:c.y},l,{x:c.x,y:l.y}].map((p)=>A(p,n));for(let p of w)v(e,p.x,p.y,f/2);return`<polygon points="${w.map((p)=>`${p.x},${p.y}`).join(" ")}" fill="${r}" ${m}/>`}return v(e,d.x,d.y,f/2),v(e,g.x,g.y,f/2),`<line x1="${d.x}" y1="${d.y}" x2="${g.x}" y2="${g.y}" ${m}/>`}if(s==="circle"){let c=C(t,"center"),l=C(t,"end");if(!c||!l)throw Error(`Invalid ${o} geometry`);let d=Math.hypot(l.x-c.x,l.y-c.y),g=A(c,n);return v(e,g.x,g.y,d+f/2),`<circle cx="${g.x}" cy="${g.y}" r="${d}" fill="${r}" ${m}/>`}if(s==="poly"){let c=K(t).map((l)=>A(l,n));if(c.length<3)throw Error(`Invalid ${o} geometry`);for(let l of c)v(e,l.x,l.y,f/2);return`<polygon points="${c.map((l)=>`${l.x},${l.y}`).join(" ")}" fill="${r}" ${m}/>`}throw Error(`Unsupported footprint geometry: ${o}`)}function Yt(t,e){let n=y(t,"keepout");if(!n)throw Error("Unsupported footprint copper zone (not a rule area)");let i=n.filter(k).filter((f)=>f[1]==="not_allowed").map((f)=>F(f)),o=L(t,"polygon");if(o.length===0)throw Error("Invalid rule area: missing polygon");let s=x.margin,a=[];for(let f of o){let u=K(f);if(u.length<3)throw Error("Invalid rule-area polygon");for(let r of u)v(e,r.x,r.y,0.075);a.push(`<polygon points="${u.map((r)=>`${r.x},${r.y}`).join(" ")}" fill="${s}" fill-opacity="0.12" stroke="${s}" stroke-width="0.15" stroke-dasharray="0.4 0.2"/>`)}return`<g data-rule-area="true"><title>${q(`Rule area (${D(t).join(", ")}): ${i.join(", ")} prohibited; outline only, not a copper subtraction`)}</title>${a.join("")}</g>`}function O(t,e,n,i,o){let s=y(t,"effects");if(z(t)||z(s))return"";let a=V(t,"at"),f=t[F(t)==="text"?1:2];if(!a||!b(f)||!f)return"";let u=f.replace(/\$\{([^}]+)\}/g,(H,N)=>o?.get(N.toUpperCase())??H),r=s?y(s,"font"):void 0,m=r?et(r):void 0,c=m?.height??1.27,l=(m?.width??c)*0.7,d=s?y(s,"justify"):void 0,g=d?.includes("left")?"start":d?.includes("right")?"end":"middle",w=d?.includes("mirror")?-1:1,p=u.split(`
468
+ `),S=c*1.5,M=c+(p.length-1)*S,I=d?.includes("top")?0:d?.includes("bottom")?-M:-M/2,P=n?a.rotation??0:-(a.rotation??0),E=P*Math.PI/180,h=[];for(let H=0;H<p.length;H++){let N=p[H].length*l,ft=g==="start"?0:g==="end"?-N:-N/2,tt=I+c/2+H*S;for(let Lt of[ft,ft+N])for(let Ct of[tt-c*0.75,tt+c*0.75]){let pt=Lt*w,mt=Ct*(n?-1:1);v(e,a.x+pt*Math.cos(E)-mt*Math.sin(E),a.y+pt*Math.sin(E)+mt*Math.cos(E))}h.push(`<tspan x="0" y="${tt}"${N>0?` textLength="${N}" lengthAdjust="spacingAndGlyphs"`:""}>${q(p[H])}</tspan>`)}let Mt=i??(n?B.text:xt(D(t)[0]??"F.SilkS")),kt=`${r?.includes("bold")||T(r??[],"bold")==="yes"?' font-weight="bold"':""}${r?.includes("italic")||T(r??[],"italic")==="yes"?' font-style="italic"':""}`;return`<text fill="${Mt}" font-size="${c}" font-family="sans-serif" text-anchor="${g}" dominant-baseline="central"${kt} transform="translate(${a.x} ${a.y}) rotate(${P}) scale(${w} ${n?-1:1})">${h.join("")}</text>`}function v(t,e,n,i=0){t.minX=Math.min(t.minX,e-i),t.maxX=Math.max(t.maxX,e+i),t.minY=Math.min(t.minY,n-i),t.maxY=Math.max(t.maxY,n+i)}function X(t){return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 100" width="100%" height="100%">
468
469
  <rect width="200" height="100" fill="#FFF0F0"/>
469
- <text x="100" y="50" fill="#CC0000" font-size="12" font-family="sans-serif" text-anchor="middle" dominant-baseline="central">${g(Z)}</text>
470
- </svg>`}var JJ=1,h="",WJ="user",i=!1,n=null,O=document.getElementById("search-input"),GJ=document.getElementById("source-select"),_J=document.getElementById("search-btn"),e=document.getElementById("results-grid"),r=document.getElementById("pagination"),FJ=document.getElementById("loading"),l=document.getElementById("preview-modal"),p=document.getElementById("modal-content"),kJ=document.getElementById("modal-close");function qJ(){let Z=window.__INITIAL_QUERY__;if(Z)O.value=Z,h=Z,L();else{let J=new URLSearchParams(window.location.search).get("q");if(J)O.value=J,h=J,L()}O.addEventListener("input",BJ),O.addEventListener("keydown",($)=>{if($.key==="Enter")$.preventDefault(),L()}),_J.addEventListener("click",L),GJ.addEventListener("change",()=>{if(WJ=GJ.value,h)L()}),kJ.addEventListener("click",t),l.addEventListener("click",($)=>{if($.target===l)t()}),document.addEventListener("keydown",($)=>{if($.key==="Escape")t()})}function BJ(){if(n)clearTimeout(n);n=window.setTimeout(()=>{let Z=O.value.trim();if(Z&&Z!==h)h=Z,JJ=1,L()},300)}async function L(){let Z=O.value.trim();if(!Z||i)return;h=Z,i=!0,QJ(!0);try{let $=new URLSearchParams({q:Z,source:WJ,page:String(JJ),limit:"20"}),J=await fetch(`/api/search?${$}`);if(!J.ok)throw Error("Search failed");let K=await J.json();AJ(K.results),vJ(K.pagination)}catch($){console.error("Search error:",$),e.innerHTML='<div class="error">Search failed. Please try again.</div>'}finally{i=!1,QJ(!1)}}function AJ(Z){if(Z.length===0){e.innerHTML='<div class="no-results">No components found. Try a different search term.</div>';return}e.innerHTML=Z.map(($)=>`
471
- <div class="card" data-uuid="${$.uuid}">
470
+ <text x="100" y="50" fill="#CC0000" font-size="12" font-family="sans-serif" text-anchor="middle" dominant-baseline="central">${q(t)}</text>
471
+ </svg>`}var ut=1,G="",St="user",st=!1,ct=null,W=document.getElementById("search-input"),$t=document.getElementById("source-select"),At=document.getElementById("search-btn"),dt=document.getElementById("results-grid"),at=document.getElementById("pagination"),Xt=document.getElementById("loading"),Z=document.getElementById("preview-modal"),J=document.getElementById("modal-content"),jt=document.getElementById("modal-close");function wt(){let e=new URLSearchParams(window.location.search).get("q");if(e)W.value=e,G=e,_();W.addEventListener("input",Ut),W.addEventListener("keydown",(n)=>{if(n.key==="Enter")n.preventDefault(),_()}),At.addEventListener("click",_),$t.addEventListener("change",()=>{if(St=$t.value,G)_()}),jt.addEventListener("click",lt),Z.addEventListener("click",(n)=>{if(n.target===Z)lt()}),document.addEventListener("keydown",(n)=>{if(n.key==="Escape")lt()})}function Ut(){if(ct)clearTimeout(ct);ct=window.setTimeout(()=>{let t=W.value.trim();if(t&&t!==G)G=t,ut=1,_()},300)}async function _(){let t=W.value.trim();if(!t||st)return;G=t,st=!0,bt(!0);try{let e=new URLSearchParams({q:t,source:St,page:String(ut),limit:"20"}),n=await fetch(`/api/search?${e}`);if(!n.ok)throw Error("Search failed");let i=await n.json();_t(i.results),zt(i.pagination)}catch(e){console.error("Search error:",e),dt.innerHTML='<div class="error">Search failed. Please try again.</div>'}finally{st=!1,bt(!1)}}function _t(t){if(t.length===0){dt.innerHTML='<div class="no-results">No components found. Try a different search term.</div>';return}dt.innerHTML=t.map((e)=>`
472
+ <div class="card" data-uuid="${e.uuid}">
472
473
  <div class="card-images">
473
- <div class="image-container symbol-container" data-uuid="${$.uuid}">
474
+ <div class="image-container symbol-container" data-uuid="${e.uuid}">
474
475
  <div class="symbol-placeholder">Loading...</div>
475
476
  <div class="image-label">Symbol</div>
476
477
  </div>
477
- <div class="image-container footprint-container" data-uuid="${$.uuid}">
478
+ <div class="image-container footprint-container" data-uuid="${e.uuid}">
478
479
  <div class="footprint-placeholder">Loading...</div>
479
480
  <div class="image-label">Footprint</div>
480
481
  </div>
481
482
  </div>
482
483
  <div class="card-info">
483
- <div class="card-title" title="${x($.title)}">${x($.title)}</div>
484
- <div class="card-package">Package: ${x($.package||"Unknown")}</div>
485
- <div class="card-owner">By: ${x($.owner.nickname||$.owner.username)}</div>
484
+ <div class="card-title" title="${R(e.title)}">${R(e.title)}</div>
485
+ <div class="card-package">Package: ${R(e.package||"Unknown")}</div>
486
+ <div class="card-owner">By: ${R(e.owner.nickname||e.owner.username)}</div>
486
487
  </div>
487
488
  <div class="card-uuid">
488
- <input type="text" value="${$.uuid}" readonly />
489
- <button class="copy-btn" data-uuid="${$.uuid}" title="Copy UUID">
489
+ <input type="text" value="${e.uuid}" readonly />
490
+ <button class="copy-btn" data-uuid="${e.uuid}" title="Copy UUID">
490
491
  <svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
491
492
  </button>
492
493
  </div>
493
494
  </div>
494
- `).join(""),document.querySelectorAll(".copy-btn").forEach(($)=>{$.addEventListener("click",(J)=>{J.stopPropagation();let K=$.dataset.uuid;if(K)HJ(K,$)})}),document.querySelectorAll(".image-container").forEach(($)=>{$.addEventListener("click",async()=>{let J=$.dataset.uuid;if(J)RJ(J)})}),Z.forEach(($)=>PJ($.uuid))}async function PJ(Z){let $=document.querySelector(`.symbol-container[data-uuid="${Z}"]`),J=document.querySelector(`.footprint-container[data-uuid="${Z}"]`);if(!$&&!J)return;try{let K=await fetch(`/api/component/${Z}`);if(!K.ok){if($)$.innerHTML='<div class="no-preview">Error</div><div class="image-label">Symbol</div>';if(J)J.innerHTML='<div class="no-preview">Error</div><div class="image-label">Footprint</div>';return}let U=await K.json();if($)if(U.symbolSexpr){let V=a(U.symbolSexpr);$.innerHTML=V+'<div class="image-label">Symbol</div>'}else $.innerHTML='<div class="no-preview">No preview</div><div class="image-label">Symbol</div>';if(J)if(U.footprintSexpr){let V=s(U.footprintSexpr);J.innerHTML=V+'<div class="image-label">Footprint</div>'}else J.innerHTML='<div class="no-preview">No preview</div><div class="image-label">Footprint</div>'}catch{if($)$.innerHTML='<div class="no-preview">Error</div><div class="image-label">Symbol</div>';if(J)J.innerHTML='<div class="no-preview">Error</div><div class="image-label">Footprint</div>'}}async function RJ(Z){l.classList.remove("hidden"),p.innerHTML='<div class="modal-loading">Loading...</div>';try{let $=await fetch(`/api/component/${Z}`);if(!$.ok)throw Error("Failed to fetch component");let J=await $.json(),K=J.symbolSexpr?a(J.symbolSexpr):"",U=J.footprintSexpr?s(J.footprintSexpr):"";p.innerHTML=`
495
+ `).join(""),document.querySelectorAll(".copy-btn").forEach((e)=>{e.addEventListener("click",(n)=>{n.stopPropagation();let i=e.dataset.uuid;if(i)Et(i,e)})}),document.querySelectorAll(".image-container").forEach((e)=>{e.addEventListener("click",async()=>{let n=e.dataset.uuid;if(n)Dt(n)})}),t.forEach((e)=>Rt(e.uuid))}async function Rt(t){let e=document.querySelector(`.symbol-container[data-uuid="${t}"]`),n=document.querySelector(`.footprint-container[data-uuid="${t}"]`);if(!e&&!n)return;try{let i=await fetch(`/api/component/${t}`);if(!i.ok){if(e)e.innerHTML='<div class="no-preview">Error</div><div class="image-label">Symbol</div>';if(n)n.innerHTML='<div class="no-preview">Error</div><div class="image-label">Footprint</div>';return}let o=await i.json();if(e)if(o.symbolSexpr){let s=ot(o.symbolSexpr);e.innerHTML=s+'<div class="image-label">Symbol</div>'}else e.innerHTML='<div class="no-preview">No preview</div><div class="image-label">Symbol</div>';if(n)if(o.footprintSexpr){let s=rt(o.footprintSexpr);n.innerHTML=s+'<div class="image-label">Footprint</div>'}else n.innerHTML='<div class="no-preview">No preview</div><div class="image-label">Footprint</div>'}catch{if(e)e.innerHTML='<div class="no-preview">Error</div><div class="image-label">Symbol</div>';if(n)n.innerHTML='<div class="no-preview">Error</div><div class="image-label">Footprint</div>'}}async function Dt(t){Z.classList.remove("hidden"),J.innerHTML='<div class="modal-loading">Loading...</div>';try{let e=await fetch(`/api/component/${t}`);if(!e.ok)throw Error("Failed to fetch component");let n=await e.json(),i=n.symbolSexpr?ot(n.symbolSexpr):"",o=n.footprintSexpr?rt(n.footprintSexpr):"";J.innerHTML=`
495
496
  <div class="modal-header">
496
- <h2>${x(J.title)}</h2>
497
+ <h2>${R(n.title)}</h2>
497
498
  <div class="modal-uuid">
498
- <code>${J.uuid}</code>
499
- <button class="copy-btn modal-copy" data-uuid="${J.uuid}">Copy UUID</button>
499
+ <code>${n.uuid}</code>
500
+ <button class="copy-btn modal-copy" data-uuid="${n.uuid}">Copy UUID</button>
500
501
  </div>
501
502
  </div>
502
503
  <div class="modal-previews">
503
504
  <div class="modal-preview">
504
505
  <h3>Symbol (KiCad)</h3>
505
- <div class="preview-svg">${K||'<div class="no-preview">No symbol preview</div>'}</div>
506
+ <div class="preview-svg">${i||'<div class="no-preview">No symbol preview</div>'}</div>
506
507
  </div>
507
508
  <div class="modal-preview">
508
509
  <h3>Footprint (KiCad)</h3>
509
- <div class="preview-svg">${U||'<div class="no-preview">No footprint preview</div>'}</div>
510
+ <div class="preview-svg">${o||'<div class="no-preview">No footprint preview</div>'}</div>
510
511
  </div>
511
512
  </div>
512
513
  <div class="modal-info">
513
- <p><strong>Description:</strong> ${x(J.description||"N/A")}</p>
514
- ${J.model3d?"<p><strong>3D Model:</strong> Available</p>":""}
514
+ <p><strong>Description:</strong> ${R(n.description||"N/A")}</p>
515
+ ${n.model3d?"<p><strong>3D Model:</strong> Available</p>":""}
515
516
  </div>
516
- `;let V=p.querySelector(".modal-copy");if(V)V.addEventListener("click",()=>{HJ(J.uuid,V)})}catch($){console.error("Modal error:",$),p.innerHTML='<div class="modal-error">Failed to load component details</div>'}}function t(){l.classList.add("hidden")}function vJ(Z){if(Z.totalPages<=1){r.innerHTML="";return}r.innerHTML=`
517
- <button class="page-btn" ${Z.hasPrev?"":"disabled"} data-page="${Z.page-1}">
517
+ `;let s=J.querySelector(".modal-copy");if(s)s.addEventListener("click",()=>{Et(n.uuid,s)})}catch(e){console.error("Modal error:",e),J.innerHTML='<div class="modal-error">Failed to load component details</div>'}}function lt(){Z.classList.add("hidden")}function zt(t){if(t.totalPages<=1){at.innerHTML="";return}at.innerHTML=`
518
+ <button class="page-btn" ${t.hasPrev?"":"disabled"} data-page="${t.page-1}">
518
519
  ← Prev
519
520
  </button>
520
- <span class="page-info">Page ${Z.page} of ${Z.totalPages}</span>
521
- <button class="page-btn" ${Z.hasNext?"":"disabled"} data-page="${Z.page+1}">
521
+ <span class="page-info">Page ${t.page} of ${t.totalPages}</span>
522
+ <button class="page-btn" ${t.hasNext?"":"disabled"} data-page="${t.page+1}">
522
523
  Next →
523
524
  </button>
524
- `,r.querySelectorAll(".page-btn").forEach(($)=>{$.addEventListener("click",()=>{let J=parseInt($.dataset.page||"1",10);if(J>0)JJ=J,L(),window.scrollTo(0,0)})})}async function HJ(Z,$){try{await navigator.clipboard.writeText(Z),$.classList.add("copied"),setTimeout(()=>$.classList.remove("copied"),1500)}catch{let J=document.createElement("input");J.value=Z,document.body.appendChild(J),J.select(),document.execCommand("copy"),document.body.removeChild(J),$.classList.add("copied"),setTimeout(()=>$.classList.remove("copied"),1500)}}function QJ(Z){FJ.classList.toggle("hidden",!Z)}function x(Z){let $=document.createElement("div");return $.textContent=Z,$.innerHTML}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",qJ);else qJ();
525
+ `,at.querySelectorAll(".page-btn").forEach((e)=>{e.addEventListener("click",()=>{let n=parseInt(e.dataset.page||"1",10);if(n>0)ut=n,_(),window.scrollTo(0,0)})})}async function Et(t,e){try{await navigator.clipboard.writeText(t),e.classList.add("copied"),setTimeout(()=>e.classList.remove("copied"),1500)}catch{let n=document.createElement("input");n.value=t,document.body.appendChild(n),n.select(),document.execCommand("copy"),document.body.removeChild(n),e.classList.add("copied"),setTimeout(()=>e.classList.remove("copied"),1500)}}function bt(t){Xt.classList.toggle("hidden",!t)}function R(t){let e=document.createElement("div");return e.textContent=t,e.innerHTML}if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",wt);else wt();
525
526
 
526
527
  </script>
527
528
  </body>