@tscircuit/copper-pour-solver 0.0.33 → 0.0.35

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.
@@ -0,0 +1,237 @@
1
+ import type { AnyCircuitElement } from "circuit-json"
2
+ import type { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
3
+ import { getElementId } from "@tscircuit/circuit-json-util"
4
+ import { getElementSubcircuitConnectivityKey } from "./getElementSubcircuitConnectivityKey"
5
+
6
+ const getElementSubcircuitId = (
7
+ element: AnyCircuitElement,
8
+ ): string | undefined => {
9
+ const subcircuitId = (element as any).subcircuit_id
10
+ return typeof subcircuitId === "string" && subcircuitId.length > 0
11
+ ? subcircuitId
12
+ : undefined
13
+ }
14
+
15
+ const getScopedSubcircuitConnectivityKey = (
16
+ subcircuitId: string | undefined,
17
+ subcircuitConnectivityMapKey: string,
18
+ ): string =>
19
+ subcircuitId
20
+ ? `subcircuit:${subcircuitId}:connectivity:${subcircuitConnectivityMapKey}`
21
+ : subcircuitConnectivityMapKey
22
+
23
+ const getDescendantSubcircuitIds = (
24
+ circuitJson: AnyCircuitElement[],
25
+ rootSubcircuitId: string | undefined,
26
+ ): Set<string> | undefined => {
27
+ if (!rootSubcircuitId) return undefined
28
+
29
+ const sourceGroupIdToSubcircuitId: Record<string, string> = {}
30
+ for (const element of circuitJson) {
31
+ if (element.type !== "source_group") continue
32
+
33
+ const sourceGroupId = (element as any).source_group_id
34
+ const subcircuitId = getElementSubcircuitId(element)
35
+ if (typeof sourceGroupId === "string" && subcircuitId) {
36
+ sourceGroupIdToSubcircuitId[sourceGroupId] = subcircuitId
37
+ }
38
+ }
39
+
40
+ const descendantSubcircuitIds = new Set([rootSubcircuitId])
41
+ let changed = true
42
+ while (changed) {
43
+ changed = false
44
+ for (const element of circuitJson) {
45
+ if (element.type !== "source_group") continue
46
+
47
+ const subcircuitId = getElementSubcircuitId(element)
48
+ if (!subcircuitId || descendantSubcircuitIds.has(subcircuitId)) continue
49
+
50
+ const parentSubcircuitId = (element as any).parent_subcircuit_id
51
+ const parentSourceGroupId = (element as any).parent_source_group_id
52
+ const parentSourceGroupSubcircuitId =
53
+ typeof parentSourceGroupId === "string"
54
+ ? sourceGroupIdToSubcircuitId[parentSourceGroupId]
55
+ : undefined
56
+
57
+ if (
58
+ (typeof parentSubcircuitId === "string" &&
59
+ descendantSubcircuitIds.has(parentSubcircuitId)) ||
60
+ (parentSourceGroupSubcircuitId &&
61
+ descendantSubcircuitIds.has(parentSourceGroupSubcircuitId))
62
+ ) {
63
+ descendantSubcircuitIds.add(subcircuitId)
64
+ changed = true
65
+ }
66
+ }
67
+ }
68
+
69
+ return descendantSubcircuitIds
70
+ }
71
+
72
+ export const buildSubcircuitConnectivityLookup = (
73
+ circuitJson: AnyCircuitElement[],
74
+ globalConnectivityMap: ReturnType<
75
+ typeof getFullConnectivityMapFromCircuitJson
76
+ >,
77
+ rootSubcircuitId?: string,
78
+ ) => {
79
+ const descendantSubcircuitIds = getDescendantSubcircuitIds(
80
+ circuitJson,
81
+ rootSubcircuitId,
82
+ )
83
+ const idToSubcircuitConnectivityKey: Record<string, string> = {}
84
+ const idToSubcircuitId: Record<string, string | undefined> = {}
85
+ const scopedKeyToIds: Record<string, string[]> = {}
86
+ const localSubcircuitConnectivityKeys = new Set<string>()
87
+
88
+ for (const element of circuitJson) {
89
+ const id = getElementId(element)
90
+ const key = getElementSubcircuitConnectivityKey(element)
91
+ const subcircuitId = getElementSubcircuitId(element)
92
+ if (id) {
93
+ idToSubcircuitId[id] = subcircuitId
94
+ }
95
+ if (
96
+ descendantSubcircuitIds &&
97
+ (!subcircuitId || !descendantSubcircuitIds.has(subcircuitId))
98
+ ) {
99
+ continue
100
+ }
101
+
102
+ if (id && key) {
103
+ const scopedKey = getScopedSubcircuitConnectivityKey(subcircuitId, key)
104
+ idToSubcircuitConnectivityKey[id] = scopedKey
105
+ localSubcircuitConnectivityKeys.add(key)
106
+ scopedKeyToIds[scopedKey] ??= []
107
+ scopedKeyToIds[scopedKey].push(id)
108
+ }
109
+ }
110
+
111
+ const generatedNetIdToSubcircuitConnectivityKey: Record<string, string> = {}
112
+ for (const [generatedNetId, connectedIds] of Object.entries(
113
+ globalConnectivityMap.netMap,
114
+ )) {
115
+ const connectedSubcircuitKeys = new Set(
116
+ connectedIds
117
+ .map((id) => idToSubcircuitConnectivityKey[id])
118
+ .filter((key): key is string => Boolean(key)),
119
+ )
120
+ const subcircuitKey = Array.from(connectedSubcircuitKeys).sort()[0]
121
+ if (subcircuitKey) {
122
+ generatedNetIdToSubcircuitConnectivityKey[generatedNetId] = subcircuitKey
123
+ }
124
+ }
125
+
126
+ const subcircuitConnectivityMap: Record<string, string> = {}
127
+ for (const [scopedKey, ids] of Object.entries(scopedKeyToIds)) {
128
+ const resolvedKeys = new Set<string>()
129
+ for (const id of ids) {
130
+ const generatedNetId = globalConnectivityMap.getNetConnectedToId(id)
131
+ const resolvedKey = generatedNetId
132
+ ? generatedNetIdToSubcircuitConnectivityKey[generatedNetId]
133
+ : undefined
134
+ resolvedKeys.add(resolvedKey ?? scopedKey)
135
+ }
136
+
137
+ if (resolvedKeys.size > 1) {
138
+ throw new Error(
139
+ `subcircuit_connectivity_map_key "${scopedKey}" maps to multiple global connectivity keys: ${Array.from(resolvedKeys).join(", ")}`,
140
+ )
141
+ }
142
+
143
+ const resolvedKey = resolvedKeys.values().next().value
144
+ if (resolvedKey) {
145
+ subcircuitConnectivityMap[scopedKey] = resolvedKey
146
+ }
147
+ }
148
+
149
+ return {
150
+ knownSubcircuitConnectivityKeys: new Set([
151
+ ...Object.keys(scopedKeyToIds),
152
+ ...localSubcircuitConnectivityKeys,
153
+ ]),
154
+ descendantSubcircuitIds,
155
+ getScopedSubcircuitConnectivityKey,
156
+ getElementSubcircuitId,
157
+ resolveSubcircuitConnectivityKey(
158
+ subcircuitConnectivityMapKey: string,
159
+ subcircuitId?: string,
160
+ ): string {
161
+ const matchingScopedKeys = Object.keys(scopedKeyToIds).filter(
162
+ (scopedKey) =>
163
+ scopedKey ===
164
+ getScopedSubcircuitConnectivityKey(
165
+ subcircuitId,
166
+ subcircuitConnectivityMapKey,
167
+ ),
168
+ )
169
+
170
+ if (
171
+ subcircuitId &&
172
+ matchingScopedKeys.length === 0 &&
173
+ descendantSubcircuitIds
174
+ ) {
175
+ matchingScopedKeys.push(
176
+ ...Object.keys(scopedKeyToIds).filter((scopedKey) =>
177
+ scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`),
178
+ ),
179
+ )
180
+ }
181
+
182
+ if (!subcircuitId) {
183
+ matchingScopedKeys.push(
184
+ ...Object.keys(scopedKeyToIds).filter((scopedKey) =>
185
+ scopedKey.endsWith(`:connectivity:${subcircuitConnectivityMapKey}`),
186
+ ),
187
+ )
188
+ }
189
+
190
+ const uniqueMatchingScopedKeys = Array.from(new Set(matchingScopedKeys))
191
+ if (uniqueMatchingScopedKeys.length === 0) {
192
+ if (subcircuitId && descendantSubcircuitIds) {
193
+ throw new Error(
194
+ `No subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" found in subcircuit "${subcircuitId}" or its child subcircuits.`,
195
+ )
196
+ }
197
+
198
+ return getScopedSubcircuitConnectivityKey(
199
+ subcircuitId,
200
+ subcircuitConnectivityMapKey,
201
+ )
202
+ }
203
+ if (uniqueMatchingScopedKeys.length > 1) {
204
+ throw new Error(
205
+ `subcircuit_connectivity_map_key "${subcircuitConnectivityMapKey}" exists in multiple subcircuits. Pass subcircuit_id to disambiguate.`,
206
+ )
207
+ }
208
+
209
+ return (
210
+ subcircuitConnectivityMap[uniqueMatchingScopedKeys[0]!] ??
211
+ uniqueMatchingScopedKeys[0]!
212
+ )
213
+ },
214
+ getSubcircuitConnectivityKeyForId(id: string): string | undefined {
215
+ if (descendantSubcircuitIds) {
216
+ const subcircuitId = idToSubcircuitId[id]
217
+ if (!subcircuitId || !descendantSubcircuitIds.has(subcircuitId)) {
218
+ return undefined
219
+ }
220
+ }
221
+
222
+ const directKey = idToSubcircuitConnectivityKey[id]
223
+ const generatedNetId = globalConnectivityMap.getNetConnectedToId(id)
224
+ if (!generatedNetId) {
225
+ return directKey
226
+ ? (subcircuitConnectivityMap[directKey] ?? directKey)
227
+ : undefined
228
+ }
229
+
230
+ return (
231
+ generatedNetIdToSubcircuitConnectivityKey[generatedNetId] ??
232
+ (directKey ? subcircuitConnectivityMap[directKey] : undefined) ??
233
+ directKey
234
+ )
235
+ },
236
+ }
237
+ }
@@ -1,17 +1,12 @@
1
1
  import type {
2
2
  AnyCircuitElement,
3
- LayerRef,
4
3
  PcbBoard,
5
4
  PcbHole,
6
5
  PcbPlatedHole,
7
- PcbPort,
8
6
  PcbSmtPad,
9
7
  PcbTrace,
10
8
  PcbVia,
11
9
  Point,
12
- SourceNet,
13
- SourcePort,
14
- SourceTrace,
15
10
  } from "circuit-json"
16
11
  import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
17
12
  import type {
@@ -23,18 +18,13 @@ import type {
23
18
  InputRectPad,
24
19
  InputTracePad,
25
20
  } from "lib/types"
21
+ import type { ConvertCircuitJsonToInputProblemOptions } from "./ConvertCircuitJsonToInputProblemOptions"
22
+ import { buildSubcircuitConnectivityLookup } from "./buildSubcircuitConnectivityLookup"
23
+ import { resolvePourConnectivityKey } from "./resolvePourConnectivityKey"
26
24
 
27
25
  export const convertCircuitJsonToInputProblem = (
28
26
  circuitJson: AnyCircuitElement[],
29
- options: {
30
- layer: LayerRef
31
- pour_connectivity_key: string
32
- pad_margin: number
33
- trace_margin: number
34
- board_edge_margin?: number
35
- cutout_margin?: number
36
- outline?: Point[]
37
- },
27
+ options: ConvertCircuitJsonToInputProblemOptions,
38
28
  ): InputProblem => {
39
29
  const pcb_board = circuitJson.find((e) => e.type === "pcb_board") as
40
30
  | PcbBoard
@@ -42,7 +32,19 @@ export const convertCircuitJsonToInputProblem = (
42
32
 
43
33
  if (!pcb_board) throw new Error("No pcb_board found in circuit json")
44
34
 
45
- const connectivityMap = getFullConnectivityMapFromCircuitJson(circuitJson)
35
+ const globalConnectivityMap =
36
+ getFullConnectivityMapFromCircuitJson(circuitJson)
37
+ const subcircuitConnectivityMap = buildSubcircuitConnectivityLookup(
38
+ circuitJson,
39
+ globalConnectivityMap,
40
+ options.subcircuit_id,
41
+ )
42
+ const pourConnectivityKey = resolvePourConnectivityKey(
43
+ circuitJson,
44
+ options,
45
+ subcircuitConnectivityMap,
46
+ )
47
+ const { getSubcircuitConnectivityKeyForId } = subcircuitConnectivityMap
46
48
 
47
49
  const pads: InputPad[] = []
48
50
 
@@ -52,9 +54,7 @@ export const convertCircuitJsonToInputProblem = (
52
54
  if (smtpad.layer !== options.layer) continue
53
55
 
54
56
  let connectivityKey: string | undefined
55
- connectivityKey = connectivityMap.getNetConnectedToId(
56
- smtpad.pcb_smtpad_id,
57
- )
57
+ connectivityKey = getSubcircuitConnectivityKeyForId(smtpad.pcb_smtpad_id)
58
58
  if (!connectivityKey) {
59
59
  connectivityKey = `unconnected:${smtpad.pcb_smtpad_id}`
60
60
  }
@@ -101,7 +101,7 @@ export const convertCircuitJsonToInputProblem = (
101
101
  const platedHole = elm as PcbPlatedHole
102
102
  if (!platedHole.layers.includes(options.layer)) continue
103
103
 
104
- let connectivityKey = connectivityMap.getNetConnectedToId(
104
+ let connectivityKey = getSubcircuitConnectivityKeyForId(
105
105
  platedHole.pcb_plated_hole_id,
106
106
  )
107
107
  if (!connectivityKey) {
@@ -189,7 +189,7 @@ export const convertCircuitJsonToInputProblem = (
189
189
  if (!via.layers.includes(options.layer)) continue
190
190
 
191
191
  const connectivityKey: string =
192
- connectivityMap.getNetConnectedToId(via.pcb_via_id) ??
192
+ getSubcircuitConnectivityKeyForId(via.pcb_via_id) ??
193
193
  `unconnected-via:${via.pcb_via_id}`
194
194
 
195
195
  pads.push({
@@ -203,7 +203,7 @@ export const convertCircuitJsonToInputProblem = (
203
203
  } as InputCircularPad)
204
204
  } else if (elm.type === "pcb_trace") {
205
205
  const trace = elm as PcbTrace
206
- const connectivityKey = connectivityMap.getNetConnectedToId(
206
+ const connectivityKey = getSubcircuitConnectivityKeyForId(
207
207
  trace.pcb_trace_id,
208
208
  )
209
209
  if (!connectivityKey) continue
@@ -271,7 +271,7 @@ export const convertCircuitJsonToInputProblem = (
271
271
  layer: options.layer,
272
272
  bounds,
273
273
  outline,
274
- connectivityKey: options.pour_connectivity_key,
274
+ connectivityKey: pourConnectivityKey,
275
275
  padMargin: options.pad_margin,
276
276
  traceMargin: options.trace_margin,
277
277
  board_edge_margin: options.board_edge_margin ?? 0,
@@ -0,0 +1,8 @@
1
+ import type { AnyCircuitElement } from "circuit-json"
2
+
3
+ export const getElementSubcircuitConnectivityKey = (
4
+ element: AnyCircuitElement,
5
+ ): string | undefined => {
6
+ const key = (element as any).subcircuit_connectivity_map_key
7
+ return typeof key === "string" && key.length > 0 ? key : undefined
8
+ }
@@ -0,0 +1,88 @@
1
+ import type { AnyCircuitElement, SourceNet } from "circuit-json"
2
+ import type { buildSubcircuitConnectivityLookup } from "./buildSubcircuitConnectivityLookup"
3
+ import type { ConvertCircuitJsonToInputProblemOptions } from "./ConvertCircuitJsonToInputProblemOptions"
4
+
5
+ type SubcircuitConnectivityLookup = ReturnType<
6
+ typeof buildSubcircuitConnectivityLookup
7
+ >
8
+
9
+ export const resolvePourConnectivityKey = (
10
+ circuitJson: AnyCircuitElement[],
11
+ options: ConvertCircuitJsonToInputProblemOptions,
12
+ subcircuitConnectivityMap: SubcircuitConnectivityLookup,
13
+ ): string => {
14
+ if (options.subcircuit_connectivity_map_key) {
15
+ return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
16
+ options.subcircuit_connectivity_map_key,
17
+ options.subcircuit_id,
18
+ )
19
+ }
20
+
21
+ if (options.source_net_id) {
22
+ const sourceNet = circuitJson.find(
23
+ (element): element is SourceNet =>
24
+ element.type === "source_net" &&
25
+ element.source_net_id === options.source_net_id,
26
+ )
27
+ if (!sourceNet) {
28
+ throw new Error(`No source_net found with id "${options.source_net_id}"`)
29
+ }
30
+ if (!sourceNet.subcircuit_connectivity_map_key) {
31
+ throw new Error(
32
+ `source_net "${options.source_net_id}" has no subcircuit_connectivity_map_key`,
33
+ )
34
+ }
35
+ return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
36
+ sourceNet.subcircuit_connectivity_map_key,
37
+ sourceNet.subcircuit_id ?? options.subcircuit_id,
38
+ )
39
+ }
40
+
41
+ if (options.source_net_name) {
42
+ const sourceNet = circuitJson.find(
43
+ (element): element is SourceNet =>
44
+ element.type === "source_net" &&
45
+ element.name === options.source_net_name &&
46
+ (!options.subcircuit_id ||
47
+ Boolean(
48
+ subcircuitConnectivityMap.descendantSubcircuitIds?.has(
49
+ element.subcircuit_id ?? "",
50
+ ),
51
+ )),
52
+ )
53
+ if (!sourceNet) {
54
+ throw new Error(
55
+ `No source_net found with name "${options.source_net_name}"`,
56
+ )
57
+ }
58
+ if (!sourceNet.subcircuit_connectivity_map_key) {
59
+ throw new Error(
60
+ `source_net "${options.source_net_name}" has no subcircuit_connectivity_map_key`,
61
+ )
62
+ }
63
+ return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
64
+ sourceNet.subcircuit_connectivity_map_key,
65
+ sourceNet.subcircuit_id ?? options.subcircuit_id,
66
+ )
67
+ }
68
+
69
+ if (options.pour_connectivity_key) {
70
+ if (
71
+ !subcircuitConnectivityMap.knownSubcircuitConnectivityKeys.has(
72
+ options.pour_connectivity_key,
73
+ )
74
+ ) {
75
+ throw new Error(
76
+ `pour_connectivity_key must be a subcircuit_connectivity_map_key. Use subcircuit_connectivity_map_key, source_net_id, or source_net_name instead of a generated connectivity-map id.`,
77
+ )
78
+ }
79
+ return subcircuitConnectivityMap.resolveSubcircuitConnectivityKey(
80
+ options.pour_connectivity_key,
81
+ options.subcircuit_id,
82
+ )
83
+ }
84
+
85
+ throw new Error(
86
+ "Copper pour requires source_net_id, source_net_name, or subcircuit_connectivity_map_key",
87
+ )
88
+ }
package/lib/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./solvers/CopperPourPipelineSolver"
2
2
  export { initializeManifoldGeometry } from "./solvers/copper-pour/manifold-runtime"
3
+ export type { ConvertCircuitJsonToInputProblemOptions } from "./circuit-json/ConvertCircuitJsonToInputProblemOptions"
3
4
  export * from "./circuit-json/convert-circuit-json-to-input-problem"
4
5
  export * from "./types"
package/package.json CHANGED
@@ -1,22 +1,27 @@
1
1
  {
2
2
  "name": "@tscircuit/copper-pour-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.33",
4
+ "version": "0.0.35",
5
5
  "scripts": {
6
6
  "format": "biome format . --write",
7
7
  "format:check": "biome format .",
8
+ "start": "cosmos",
9
+ "cosmos": "react-cosmos",
10
+ "build:site": "cosmos-export",
8
11
  "build": "tsup-node lib/index.ts --dts --format esm"
9
12
  },
10
13
  "type": "module",
11
14
  "devDependencies": {
12
15
  "@biomejs/biome": "^2.2.4",
13
16
  "@flatten-js/core": "^1.6.2",
14
- "@tscircuit/math-utils": "^0.0.25",
17
+ "@tscircuit/circuit-json-util": "^0.0.77",
18
+ "@tscircuit/core": "^0.0.988",
19
+ "@tscircuit/math-utils": "^0.0.29",
15
20
  "@tscircuit/solver-utils": "^0.0.14",
16
- "circuit-json-to-connectivity-map": "^0.0.23",
17
21
  "@types/bun": "latest",
18
22
  "bun-match-svg": "^0.0.13",
19
23
  "circuit-json": "^0.0.432",
24
+ "circuit-json-to-connectivity-map": "^0.0.23",
20
25
  "circuit-to-svg": "^0.0.350",
21
26
  "react-cosmos": "^7.0.0",
22
27
  "react-cosmos-plugin-vite": "^7.0.0",
@@ -0,0 +1,214 @@
1
+ import type React from "react"
2
+
3
+ const codeSample = `import {
4
+ CopperPourPipelineSolver,
5
+ convertCircuitJsonToInputProblem,
6
+ initializeManifoldGeometry,
7
+ } from "@tscircuit/copper-pour-solver"
8
+
9
+ await initializeManifoldGeometry()
10
+
11
+ const inputProblem = convertCircuitJsonToInputProblem(circuitJson, {
12
+ layer: "top",
13
+ source_net_name: "GND",
14
+ pad_margin: 0.4,
15
+ trace_margin: 0.2,
16
+ board_edge_margin: 0.1,
17
+ cutout_margin: 0.2,
18
+ })
19
+
20
+ const solver = new CopperPourPipelineSolver(inputProblem)
21
+ const { brep_shapes } = solver.getOutput()`
22
+
23
+ const developmentSample = `bun install
24
+ bun run build
25
+ bun test
26
+ bun start
27
+ bun run build:site`
28
+
29
+ export default function WelcomePage() {
30
+ return (
31
+ <main style={styles.main}>
32
+ <header style={styles.header}>
33
+ <p style={styles.eyebrow}>tscircuit geometry</p>
34
+ <h1 style={styles.title}>@tscircuit/copper-pour-solver</h1>
35
+ <p style={styles.lede}>
36
+ Generate PCB copper pour B-Rep polygons from Circuit JSON or from a
37
+ direct geometry input format.
38
+ </p>
39
+ <nav style={styles.actions} aria-label="Project links">
40
+ <a style={{ ...styles.button, ...styles.primaryButton }} href="/">
41
+ Cosmos docs
42
+ </a>
43
+ <a
44
+ style={styles.button}
45
+ href="https://github.com/tscircuit/copper-pour-solver"
46
+ >
47
+ GitHub
48
+ </a>
49
+ <a
50
+ style={styles.button}
51
+ href="https://www.npmjs.com/package/@tscircuit/copper-pour-solver"
52
+ >
53
+ npm
54
+ </a>
55
+ </nav>
56
+ </header>
57
+
58
+ <section style={styles.section}>
59
+ <h2 style={styles.heading}>Install</h2>
60
+ <pre style={styles.pre}>
61
+ <code>bun add @tscircuit/copper-pour-solver</code>
62
+ </pre>
63
+ </section>
64
+
65
+ <section style={styles.section}>
66
+ <h2 style={styles.heading}>Use With Circuit JSON</h2>
67
+ <p style={styles.copy}>
68
+ Initialize the Manifold geometry runtime once, convert Circuit JSON
69
+ into a solver input problem by source net name, then read the
70
+ generated B-Rep shapes.
71
+ </p>
72
+ <pre style={styles.pre}>
73
+ <code>{codeSample}</code>
74
+ </pre>
75
+ </section>
76
+
77
+ <section style={styles.section}>
78
+ <h2 style={styles.heading}>What The Converter Reads</h2>
79
+ <div style={styles.grid}>
80
+ <div style={styles.item}>
81
+ <strong>Board geometry</strong>
82
+ <span>
83
+ Board bounds, board outlines, and optional pour outlines.
84
+ </span>
85
+ </div>
86
+ <div style={styles.item}>
87
+ <strong>Obstacles</strong>
88
+ <span>
89
+ SMT pads, plated holes, mechanical holes, vias, traces, and
90
+ cutouts.
91
+ </span>
92
+ </div>
93
+ <div style={styles.item}>
94
+ <strong>Connectivity</strong>
95
+ <span>
96
+ Source net ids, source net names, or stable
97
+ subcircuit_connectivity_map_key values.
98
+ </span>
99
+ </div>
100
+ <div style={styles.item}>
101
+ <strong>Clearances</strong>
102
+ <span>Pad, trace, board edge, and cutout margin controls.</span>
103
+ </div>
104
+ </div>
105
+ </section>
106
+
107
+ <section style={styles.section}>
108
+ <h2 style={styles.heading}>Development</h2>
109
+ <pre style={styles.pre}>
110
+ <code>{developmentSample}</code>
111
+ </pre>
112
+ </section>
113
+ </main>
114
+ )
115
+ }
116
+
117
+ const styles: Record<string, React.CSSProperties> = {
118
+ main: {
119
+ width: "min(980px, calc(100% - 32px))",
120
+ margin: "0 auto",
121
+ padding: "48px 0 64px",
122
+ color: "#17202a",
123
+ fontFamily:
124
+ 'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
125
+ lineHeight: 1.55,
126
+ },
127
+ header: {
128
+ borderBottom: "1px solid #d7dee4",
129
+ paddingBottom: 28,
130
+ marginBottom: 32,
131
+ },
132
+ eyebrow: {
133
+ margin: "0 0 10px",
134
+ color: "#0b766e",
135
+ fontSize: 13,
136
+ fontWeight: 700,
137
+ textTransform: "uppercase",
138
+ letterSpacing: 0,
139
+ },
140
+ title: {
141
+ margin: "0 0 12px",
142
+ fontSize: "clamp(2rem, 7vw, 4rem)",
143
+ lineHeight: 1,
144
+ letterSpacing: 0,
145
+ },
146
+ lede: {
147
+ maxWidth: 760,
148
+ margin: "0 0 16px",
149
+ color: "#5f6f7a",
150
+ fontSize: "1.12rem",
151
+ },
152
+ actions: {
153
+ display: "flex",
154
+ flexWrap: "wrap",
155
+ gap: 10,
156
+ marginTop: 22,
157
+ },
158
+ button: {
159
+ display: "inline-flex",
160
+ alignItems: "center",
161
+ minHeight: 38,
162
+ padding: "7px 12px",
163
+ border: "1px solid #d7dee4",
164
+ borderRadius: 6,
165
+ textDecoration: "none",
166
+ color: "#17202a",
167
+ background: "#ffffff",
168
+ fontWeight: 650,
169
+ },
170
+ primaryButton: {
171
+ color: "#ffffff",
172
+ background: "#0b766e",
173
+ borderColor: "#0b766e",
174
+ },
175
+ section: {
176
+ marginTop: 34,
177
+ },
178
+ heading: {
179
+ margin: "0 0 12px",
180
+ fontSize: "1.35rem",
181
+ letterSpacing: 0,
182
+ },
183
+ copy: {
184
+ maxWidth: 760,
185
+ margin: "0 0 16px",
186
+ color: "#34444f",
187
+ },
188
+ pre: {
189
+ overflowX: "auto",
190
+ margin: "14px 0 24px",
191
+ padding: 16,
192
+ border: "1px solid #d7dee4",
193
+ borderRadius: 8,
194
+ background: "#f7f9fb",
195
+ color: "#10212d",
196
+ fontFamily:
197
+ '"SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace',
198
+ fontSize: 14,
199
+ },
200
+ grid: {
201
+ display: "grid",
202
+ gridTemplateColumns: "repeat(auto-fit, minmax(230px, 1fr))",
203
+ gap: 14,
204
+ margin: "18px 0 8px",
205
+ },
206
+ item: {
207
+ display: "grid",
208
+ gap: 4,
209
+ border: "1px solid #d7dee4",
210
+ borderRadius: 8,
211
+ padding: 14,
212
+ background: "#ffffff",
213
+ },
214
+ }