@protoboardai/ext-docs 0.0.0-stage → 0.2.0
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/LICENSE +202 -0
- package/NOTICE +18 -0
- package/README.md +78 -2
- package/evals/build-datasheet/case.json +147 -0
- package/fonts/KodeMono-Bold.ttf +0 -0
- package/fonts/KodeMono-Medium.ttf +0 -0
- package/fonts/KodeMono-Regular.ttf +0 -0
- package/fonts/KodeMono-SemiBold.ttf +0 -0
- package/fonts/OFL.txt +93 -0
- package/lib/blocks.ts +268 -0
- package/lib/bom.ts +126 -0
- package/lib/build.ts +202 -0
- package/lib/charts.ts +241 -0
- package/lib/context.ts +122 -0
- package/lib/derived.ts +181 -0
- package/lib/docs/assembly.ts +674 -0
- package/lib/docs/common.ts +81 -0
- package/lib/docs/datasheet.ts +875 -0
- package/lib/docs/product.ts +45 -0
- package/lib/figures.ts +195 -0
- package/lib/format.ts +221 -0
- package/lib/html.ts +135 -0
- package/lib/model.ts +312 -0
- package/lib/package/assembly.ts +268 -0
- package/lib/package/common.ts +109 -0
- package/lib/package/plan.ts +432 -0
- package/lib/package/spec.ts +203 -0
- package/lib/png.ts +91 -0
- package/lib/render/cdp.ts +5 -0
- package/lib/render/page/render.html +17 -0
- package/lib/render/page/render.js +390 -0
- package/lib/render/renderer.ts +161 -0
- package/lib/render/server.ts +105 -0
- package/lib/standin.ts +36 -0
- package/lib/standins/multirotor.ts +283 -0
- package/lib/templates/doc.css +247 -0
- package/lib/templates/flow.js +131 -0
- package/lib/testdata.ts +85 -0
- package/lib/types.ts +120 -0
- package/lib/values.ts +309 -0
- package/lib/verify.ts +276 -0
- package/ops/docs.ts +52 -0
- package/ops/package.ts +9 -0
- package/package.json +51 -4
- package/protoboard-extension.json +83 -0
- package/skills/uhd-tech-docs/SKILL.md +234 -0
- package/skills/uhd-tech-docs/references/docspec.md +56 -0
- package/skills/uhd-tech-docs/references/house-style.md +50 -0
- package/skills/uhd-tech-docs/references/style-study.md +270 -0
- package/skills/uhd-tech-docs/references/test-data.md +49 -0
- package/skills/uhd-tech-docs/references/value-queries.md +67 -0
- package/skills/uhd-tech-docs/templates/test-data.example.json +33 -0
- package/skills/uhd-tech-docs/templates/test-data.schema.json +213 -0
- package/skills/uhd-tech-docs-verify/SKILL.md +104 -0
- package/skills/uhd-tech-docs-verify/references/visual-review.md +43 -0
package/lib/standin.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stand-in test data for a system, generated from its model by the doc
|
|
3
|
+
* spec's recipe (`testData.standins`, a module in lib/standins/). Writes
|
|
4
|
+
* `docs/test-data/<test-id>.json` with status "standin". A file whose status
|
|
5
|
+
* is "measured" is never touched; existing stand-ins are refreshed (they are
|
|
6
|
+
* derived data, so regenerating them from the current model is always safe).
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
import type { BuiltContext } from "./context.ts";
|
|
11
|
+
import type { TestData } from "./testdata.ts";
|
|
12
|
+
|
|
13
|
+
export async function writeStandins(ctx: BuiltContext): Promise<{ written: string[]; kept: string[] }> {
|
|
14
|
+
const recipe = ctx.sys.config.testData?.standins;
|
|
15
|
+
if (!recipe) return { written: [], kept: [] };
|
|
16
|
+
if (!/^[\w-]+$/.test(recipe)) throw new Error(`testData.standins "${recipe}" is not a recipe name (lib/standins/<name>.ts)`);
|
|
17
|
+
const mod = await import(new URL(`./standins/${recipe}.ts`, import.meta.url).href);
|
|
18
|
+
const sets: TestData[] = mod.standins(ctx);
|
|
19
|
+
const dir = join(ctx.sys.absDocs, "test-data");
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
const written: string[] = [];
|
|
22
|
+
const kept: string[] = [];
|
|
23
|
+
for (const t of sets) {
|
|
24
|
+
const file = join(dir, `${t.id}.json`);
|
|
25
|
+
if (existsSync(file)) {
|
|
26
|
+
const cur = JSON.parse(readFileSync(file, "utf8"));
|
|
27
|
+
if (cur.status === "measured") {
|
|
28
|
+
kept.push(t.id);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(file, JSON.stringify(t, null, 2) + "\n");
|
|
33
|
+
written.push(t.id);
|
|
34
|
+
}
|
|
35
|
+
return { written, kept };
|
|
36
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stand-in test data for a multirotor, derived from the model so documents
|
|
3
|
+
* have plausible curves before anything is measured. Every file is written
|
|
4
|
+
* with status "standin", lists the model queries it was derived from, and
|
|
5
|
+
* states each assumption the model could not supply. The build never
|
|
6
|
+
* overwrites a file whose status is "measured".
|
|
7
|
+
*
|
|
8
|
+
* Model inputs: the motor maker's full thrust table (performance.thrust_tests,
|
|
9
|
+
* interpolated row to row by src/system/propulsion.ts), motor idle current, battery voltage / capacity / cell count, part
|
|
10
|
+
* masses, avionics supply figures (O4 min supply power, GNSS supply current).
|
|
11
|
+
*/
|
|
12
|
+
import type { ModuleDef } from "@deltarobotics/uhd";
|
|
13
|
+
import { category } from "../model.ts";
|
|
14
|
+
import { modelMass } from "../derived.ts";
|
|
15
|
+
import { fullThrottle, thrustAtThrottle, thrustAtThrust, thrustTests } from "@deltarobotics/uhd";
|
|
16
|
+
import type { DocContext } from "../values.ts";
|
|
17
|
+
import type { TestData } from "../testdata.ts";
|
|
18
|
+
|
|
19
|
+
const GEN = "@protoboardai/ext-docs lib/standins/multirotor.ts";
|
|
20
|
+
const r = (x: number, d = 2) => Number(x.toFixed(d));
|
|
21
|
+
|
|
22
|
+
function first(ctx: DocContext, cat: string): ModuleDef | undefined {
|
|
23
|
+
return ctx.scene.assembly.instances.find((i) => i.kind === "module" && category(i.def) === cat)?.def;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function standins(ctx: DocContext): TestData[] {
|
|
27
|
+
const system = ctx.sys.system.id;
|
|
28
|
+
const motor = first(ctx, "motor");
|
|
29
|
+
const battery = first(ctx, "battery");
|
|
30
|
+
if (!motor || !battery) return [];
|
|
31
|
+
const motorCount = ctx.scene.assembly.instances.filter((i) => i.kind === "module" && category(i.def) === "motor").length;
|
|
32
|
+
const test = thrustTests(motor)[0];
|
|
33
|
+
if (!test) return [];
|
|
34
|
+
const bOut = battery.interfaces.find((i) => i.parameters?.some((p) => p.id === "capacity"))!;
|
|
35
|
+
const bp = (id: string) => bOut.parameters!.find((p) => p.id === id)!.value as number;
|
|
36
|
+
const V = bp("voltage");
|
|
37
|
+
const cap = bp("capacity");
|
|
38
|
+
const cells = bp("cell_count");
|
|
39
|
+
const bq = (id: string) => `def:${battery.id}:interfaces[id=${bOut.id}].parameters[id=${id}].value`;
|
|
40
|
+
const tq = (k: string) => `def:${motor.id}:traits[type=performance].params.thrust_tests[0].${k}`;
|
|
41
|
+
|
|
42
|
+
// everything propulsive comes from the maker's rows, interpolated linearly between them
|
|
43
|
+
const top = fullThrottle(test);
|
|
44
|
+
const Tfull = top.thrust_g;
|
|
45
|
+
const Ifull = top.current_A;
|
|
46
|
+
const at = (throttlePct: number) => thrustAtThrottle(test, throttlePct);
|
|
47
|
+
/** Pack current for all motors producing `thrustG` each: table power at that thrust over the pack's nominal voltage. */
|
|
48
|
+
const packCurrentFor = (thrustG: number) => {
|
|
49
|
+
const row = thrustAtThrust(test, thrustG);
|
|
50
|
+
return row ? (motorCount * row.power_W) / V : undefined;
|
|
51
|
+
};
|
|
52
|
+
const currentAt = (t: number) => at(t * 100).current_A;
|
|
53
|
+
|
|
54
|
+
// avionics: O4 minimum supply power from the model; GNSS supply current; FC + RX assumed
|
|
55
|
+
const o4 = first(ctx, "video");
|
|
56
|
+
const o4W = (o4?.interfaces.find((i) => i.id === "vcc")?.parameters?.find((p) => p.id === "min_supply_power")?.value as number) ?? 0;
|
|
57
|
+
const gnss = first(ctx, "gnss");
|
|
58
|
+
const gnssmA = (gnss?.domains?.find((d) => d.domain === "electrical")?.metadata as any)?.supply_current_mA ?? 0;
|
|
59
|
+
const FC_RX_W = 2.5; // assumption: FC + receiver draw, not in the model
|
|
60
|
+
const BEC_EFF = 0.85; // assumption
|
|
61
|
+
const avionicsW = (o4W + (gnssmA / 1000) * 5 + FC_RX_W) / BEC_EFF;
|
|
62
|
+
const avionicsA = avionicsW / V;
|
|
63
|
+
|
|
64
|
+
// mass: systemMass (stated weights, else CAD volume × material density), plus an
|
|
65
|
+
// assumed allowance for the parts the model has no mass for (cables, capacitor)
|
|
66
|
+
const sm = modelMass(ctx);
|
|
67
|
+
const known = sm.totalG;
|
|
68
|
+
const missing = sm.missing;
|
|
69
|
+
const UNKNOWN_ALLOWANCE_G = 20; // assumption: leads, cables and capacitor with no mass in the model
|
|
70
|
+
const auw = known + UNKNOWN_ALLOWANCE_G;
|
|
71
|
+
|
|
72
|
+
// hover: the table's electrical power at thrust = AUW / motors (interpolated between rows)
|
|
73
|
+
const hoverT = auw / motorCount;
|
|
74
|
+
const hoverIprop = packCurrentFor(hoverT) ?? motorCount * Ifull;
|
|
75
|
+
const hoverI = hoverIprop + avionicsA;
|
|
76
|
+
const USABLE = 0.8; // assumption: usable fraction of rated capacity
|
|
77
|
+
const minutes = (I: number) => ((cap / 1000) * USABLE * 60) / I;
|
|
78
|
+
|
|
79
|
+
const base = {
|
|
80
|
+
$schema: "uhd-test-data/1",
|
|
81
|
+
status: "standin" as const,
|
|
82
|
+
subject: { system, modules: [motor.id, battery.id] },
|
|
83
|
+
};
|
|
84
|
+
const derivedFrom = [tq("rows"), tq("propeller"), tq("supply_V"), bq("voltage"), bq("capacity")];
|
|
85
|
+
const assumptions = [
|
|
86
|
+
`motor figures interpolate linearly between the ${test.rows.length} rows of the maker's table (0 at 0 % throttle); pack current = table electrical power ÷ ${V} V nominal`,
|
|
87
|
+
`maker's table used the ${test.propeller} prop at ${test.supply_V} V, not the fitted propeller`,
|
|
88
|
+
`avionics: DJI O4 minimum supply power ${o4W} W, GNSS ${gnssmA} mA at 5 V, FC + receiver ${FC_RX_W} W (assumed), BEC efficiency ${BEC_EFF * 100} % (assumed)`,
|
|
89
|
+
`all-up weight = ${r(known, 1)} g from systemMass (derived:mass.all_up, of which ${r(sm.assumedG, 1)} g is CAD volume × assumed material) + ${UNKNOWN_ALLOWANCE_G} g assumed for the ${missing.length} parts with no mass in the model (${missing.map((e) => e.def.name).join(", ")})`,
|
|
90
|
+
];
|
|
91
|
+
const generatedAt = new Date().toISOString().slice(0, 10);
|
|
92
|
+
|
|
93
|
+
// 1 throttle sweep (static)
|
|
94
|
+
const sweep: TestData = {
|
|
95
|
+
...base,
|
|
96
|
+
id: "throttle-sweep",
|
|
97
|
+
title: "Static thrust and current vs throttle (whole vehicle)",
|
|
98
|
+
conditions: [
|
|
99
|
+
{ name: "Supply", value: test.supply_V, unit: "V" },
|
|
100
|
+
{ name: "Propeller (maker's test)", value: test.propeller, unit: "" },
|
|
101
|
+
{ name: "Motors", value: motorCount, unit: "" },
|
|
102
|
+
],
|
|
103
|
+
provenance: { method: "Stand-in: the motor maker's thrust table row by row, scaled to all motors, plus avionics draw.", derivedFrom, assumptions, generator: GEN, generatedAt },
|
|
104
|
+
columns: [
|
|
105
|
+
{ id: "throttle", label: "Throttle", unit: "%" },
|
|
106
|
+
{ id: "thrust_motor", label: "Thrust per motor", unit: "g" },
|
|
107
|
+
{ id: "current_motor", label: "Current per motor", unit: "A" },
|
|
108
|
+
{ id: "thrust_total", label: "Total thrust", unit: "g" },
|
|
109
|
+
{ id: "current_total", label: "Total current", unit: "A" },
|
|
110
|
+
{ id: "power_total", label: "Electrical power", unit: "W" },
|
|
111
|
+
],
|
|
112
|
+
rows: Array.from({ length: 11 }, (_, i) => {
|
|
113
|
+
const t = i / 10;
|
|
114
|
+
const Tm = at(t * 100).thrust_g;
|
|
115
|
+
const Im = currentAt(t);
|
|
116
|
+
const It = motorCount * Im + avionicsA;
|
|
117
|
+
return [i * 10, r(Tm, 0), r(Im, 2), r(motorCount * Tm, 0), r(It, 1), r(It * test.supply_V, 0)];
|
|
118
|
+
}),
|
|
119
|
+
summary: [
|
|
120
|
+
{ id: "thrust_total_full", label: "Total static thrust at 100 %", value: r(motorCount * Tfull, 0), unit: "g" },
|
|
121
|
+
{ id: "current_total_full", label: "Total current at 100 %", value: r(motorCount * Ifull + avionicsA, 1), unit: "A" },
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// 2 hover current
|
|
126
|
+
const hover: TestData = {
|
|
127
|
+
...base,
|
|
128
|
+
id: "hover-current",
|
|
129
|
+
title: "Hover current and all-up-weight sensitivity",
|
|
130
|
+
conditions: [
|
|
131
|
+
{ name: "Battery nominal", value: V, unit: "V" },
|
|
132
|
+
{ name: "All-up weight (estimate)", value: r(auw, 0), unit: "g" },
|
|
133
|
+
],
|
|
134
|
+
provenance: { method: "Stand-in: hover thrust = AUW / motors; motor power interpolated from the maker's thrust table at that thrust; plus avionics.", derivedFrom: [...derivedFrom, "derived:mass.all_up"], assumptions, generator: GEN, generatedAt },
|
|
135
|
+
columns: [
|
|
136
|
+
{ id: "auw", label: "All-up weight", unit: "g" },
|
|
137
|
+
{ id: "thrust_motor", label: "Hover thrust per motor", unit: "g" },
|
|
138
|
+
{ id: "current_total", label: "Hover current", unit: "A" },
|
|
139
|
+
{ id: "power_total", label: "Hover power", unit: "W" },
|
|
140
|
+
],
|
|
141
|
+
rows: [-100, -50, 0, 50, 100, 150].map((d) => {
|
|
142
|
+
const w = auw + d;
|
|
143
|
+
const I = (packCurrentFor(w / motorCount) ?? motorCount * Ifull) + avionicsA;
|
|
144
|
+
return [r(w, 0), r(w / motorCount, 0), r(I, 2), r(I * V, 0)];
|
|
145
|
+
}),
|
|
146
|
+
summary: [
|
|
147
|
+
{ id: "auw_g", label: "All-up weight (estimate)", value: r(auw, 0), unit: "g" },
|
|
148
|
+
{ id: "hover_current_A", label: "Hover current", value: r(hoverI, 1), unit: "A" },
|
|
149
|
+
{ id: "hover_power_W", label: "Hover power", value: r(hoverI * V, 0), unit: "W" },
|
|
150
|
+
{ id: "avionics_current_A", label: "Avionics current", value: r(avionicsA, 2), unit: "A" },
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// 3 flight time
|
|
155
|
+
const regimes: [string, number][] = [
|
|
156
|
+
["Hover", hoverI],
|
|
157
|
+
["Cruise (1.5 × hover thrust)", (packCurrentFor(1.5 * hoverT) ?? motorCount * Ifull) + avionicsA],
|
|
158
|
+
["Freestyle mix", 0],
|
|
159
|
+
["Full throttle", motorCount * Ifull + avionicsA],
|
|
160
|
+
];
|
|
161
|
+
const mix = 0.75 * regimes[1][1] + 0.2 * (motorCount * at(50).current_A + avionicsA) + 0.05 * regimes[3][1];
|
|
162
|
+
regimes[2][1] = mix;
|
|
163
|
+
const flight: TestData = {
|
|
164
|
+
...base,
|
|
165
|
+
id: "flight-time",
|
|
166
|
+
title: "Flight-time estimate by regime",
|
|
167
|
+
conditions: [
|
|
168
|
+
{ name: "Capacity", value: cap, unit: "mAh" },
|
|
169
|
+
{ name: "Usable fraction (assumed)", value: USABLE * 100, unit: "%" },
|
|
170
|
+
{ name: "Freestyle mix", value: "75 % cruise, 20 % half throttle, 5 % full", unit: "" },
|
|
171
|
+
],
|
|
172
|
+
provenance: { method: "Stand-in: time = capacity × usable fraction ÷ average current. Static-thrust currents: pessimistic for forward flight.", derivedFrom: [...derivedFrom, "test:hover-current:summary[id=hover_current_A].value"], assumptions: [...assumptions, `usable capacity ${USABLE * 100} % of rated`], generator: GEN, generatedAt },
|
|
173
|
+
columns: [
|
|
174
|
+
{ id: "regime", label: "Regime", unit: "" },
|
|
175
|
+
{ id: "current", label: "Average current", unit: "A" },
|
|
176
|
+
{ id: "minutes", label: "Flight time", unit: "min" },
|
|
177
|
+
],
|
|
178
|
+
rows: regimes.map(([n, I]) => [n, r(I, 1), r(minutes(I), 1)]),
|
|
179
|
+
summary: [
|
|
180
|
+
{ id: "hover_min", label: "Hover", value: r(minutes(hoverI), 1), unit: "min" },
|
|
181
|
+
{ id: "mix_min", label: "Freestyle mix", value: r(minutes(mix), 1), unit: "min" },
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// 4 thrust-to-weight
|
|
186
|
+
const volts = [cells * 4.2, V, cells * 3.5];
|
|
187
|
+
const twr: TestData = {
|
|
188
|
+
...base,
|
|
189
|
+
id: "thrust-to-weight",
|
|
190
|
+
title: "Thrust-to-weight ratio vs pack voltage",
|
|
191
|
+
conditions: [
|
|
192
|
+
{ name: "All-up weight (estimate)", value: r(auw, 0), unit: "g" },
|
|
193
|
+
{ name: "Reference supply (maker's test)", value: test.supply_V, unit: "V" },
|
|
194
|
+
],
|
|
195
|
+
provenance: { method: "Stand-in: static thrust scaled with (V / test V)² (thrust ~ rpm², rpm ~ V), divided by the AUW estimate.", derivedFrom: [...derivedFrom, "derived:mass.all_up"], assumptions, generator: GEN, generatedAt },
|
|
196
|
+
columns: [
|
|
197
|
+
{ id: "voltage", label: "Pack voltage", unit: "V" },
|
|
198
|
+
{ id: "thrust_total", label: "Total static thrust", unit: "g" },
|
|
199
|
+
{ id: "twr", label: "Thrust-to-weight", unit: "" },
|
|
200
|
+
],
|
|
201
|
+
rows: volts.map((v) => {
|
|
202
|
+
const T = motorCount * Tfull * (v / test.supply_V) ** 2;
|
|
203
|
+
return [r(v, 1), r(T, 0), r(T / auw, 1)];
|
|
204
|
+
}),
|
|
205
|
+
summary: [{ id: "twr_nominal", label: "Thrust-to-weight at nominal voltage", value: r((motorCount * Tfull * (V / test.supply_V) ** 2) / auw, 1), unit: "" }],
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// 5 ESC temperature over a punch-out
|
|
209
|
+
const AMB = 25, RTH = 2.0, CTH = 20, RLOSS = 0.004, SW = 1.5; // assumptions
|
|
210
|
+
const esc = first(ctx, "esc");
|
|
211
|
+
const temps: (number | string)[][] = [];
|
|
212
|
+
let T = AMB + 8;
|
|
213
|
+
const thrOf = (s: number) => (s >= 2 && s < 6 ? 1 : 0.35);
|
|
214
|
+
for (let s = 0; s <= 30; s += 0.5) {
|
|
215
|
+
const th = thrOf(s);
|
|
216
|
+
const Ich = currentAt(th);
|
|
217
|
+
const P = motorCount * (Ich ** 2 * RLOSS + SW * th);
|
|
218
|
+
if (s > 0) T += ((P - (T - AMB) / RTH) / CTH) * 0.5;
|
|
219
|
+
temps.push([s, th * 100, r(Ich, 1), r(T, 1)]);
|
|
220
|
+
}
|
|
221
|
+
const peak = Math.max(...temps.map((x) => Number(x[3])));
|
|
222
|
+
const escTemp: TestData = {
|
|
223
|
+
...base,
|
|
224
|
+
id: "esc-temp-punchout",
|
|
225
|
+
title: "ESC board temperature over a 4 s punch-out",
|
|
226
|
+
subject: { system, modules: [esc?.id ?? "esc", motor.id] },
|
|
227
|
+
conditions: [
|
|
228
|
+
{ name: "Ambient", value: AMB, unit: "°C" },
|
|
229
|
+
{ name: "Punch-out", value: "100 % from 2 s to 6 s, 35 % otherwise", unit: "" },
|
|
230
|
+
],
|
|
231
|
+
provenance: {
|
|
232
|
+
method: "Stand-in: lumped thermal model C·dT/dt = P − (T − ambient)/R with conduction and switching losses per channel.",
|
|
233
|
+
derivedFrom: [tq("rows"), ...(esc ? [`def:${esc.id}:interfaces[id=motor_1].parameters[id=max_current].value`] : [])],
|
|
234
|
+
assumptions: [
|
|
235
|
+
`thermal resistance ${RTH} K/W and capacity ${CTH} J/K (board in prop wash) — not in the model`,
|
|
236
|
+
`effective conduction resistance ${RLOSS * 1000} mΩ per channel and ${SW} W switching loss per channel at full throttle — not in the model`,
|
|
237
|
+
"starting 8 °C above ambient after the previous flight segment",
|
|
238
|
+
],
|
|
239
|
+
generator: GEN,
|
|
240
|
+
generatedAt,
|
|
241
|
+
},
|
|
242
|
+
columns: [
|
|
243
|
+
{ id: "time", label: "Time", unit: "s" },
|
|
244
|
+
{ id: "throttle", label: "Throttle", unit: "%" },
|
|
245
|
+
{ id: "current_channel", label: "Current per channel", unit: "A" },
|
|
246
|
+
{ id: "temperature", label: "ESC temperature", unit: "°C" },
|
|
247
|
+
],
|
|
248
|
+
rows: temps,
|
|
249
|
+
summary: [{ id: "peak_C", label: "Peak temperature", value: r(peak, 1), unit: "°C" }],
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// 6 battery discharge
|
|
253
|
+
const OCV = [4.2, 4.08, 3.98, 3.9, 3.84, 3.8, 3.77, 3.74, 3.7, 3.62, 3.45]; // generic LiPo curve (assumption)
|
|
254
|
+
const RCELL = 0.004; // assumption
|
|
255
|
+
const dis: TestData = {
|
|
256
|
+
...base,
|
|
257
|
+
id: "battery-discharge",
|
|
258
|
+
title: "Pack voltage vs capacity used",
|
|
259
|
+
subject: { system, modules: [battery.id] },
|
|
260
|
+
conditions: [
|
|
261
|
+
{ name: "Cells", value: cells, unit: "" },
|
|
262
|
+
{ name: "Hover current (stand-in)", value: r(hoverI, 1), unit: "A" },
|
|
263
|
+
{ name: "Freestyle average (stand-in)", value: r(mix, 1), unit: "A" },
|
|
264
|
+
],
|
|
265
|
+
provenance: {
|
|
266
|
+
method: "Stand-in: generic LiPo open-circuit curve per cell minus I·R sag, scaled to the pack.",
|
|
267
|
+
derivedFrom: [bq("cell_count"), bq("capacity"), "test:hover-current:summary[id=hover_current_A].value"],
|
|
268
|
+
assumptions: ["generic LiPo open-circuit voltage curve", `internal resistance ${RCELL * 1000} mΩ per cell — not in the model`],
|
|
269
|
+
generator: GEN,
|
|
270
|
+
generatedAt,
|
|
271
|
+
},
|
|
272
|
+
columns: [
|
|
273
|
+
{ id: "used", label: "Capacity used", unit: "mAh" },
|
|
274
|
+
{ id: "v_rest", label: "Open circuit", unit: "V" },
|
|
275
|
+
{ id: "v_hover", label: "At hover current", unit: "V" },
|
|
276
|
+
{ id: "v_mix", label: "At freestyle average", unit: "V" },
|
|
277
|
+
],
|
|
278
|
+
rows: OCV.map((v, i) => [r((cap * i) / 10, 0), r(v * cells, 2), r((v - hoverI * RCELL) * cells, 2), r((v - mix * RCELL) * cells, 2)]),
|
|
279
|
+
summary: [{ id: "sag_mix_V", label: "Sag at freestyle average", value: r(mix * RCELL * cells, 2), unit: "V" }],
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return [sweep, hover, flight, twr, escTemp, dis];
|
|
283
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/* uhd-tech-docs print theme — light pages, Protoboard type and accent.
|
|
2
|
+
Rules: skills/uhd-tech-docs/references/style-study.md ("Rules we adopt"). */
|
|
3
|
+
@font-face { font-family: "Kode Mono"; src: url("__FONTS__/KodeMono-Regular.ttf") format("truetype"); font-weight: 400; }
|
|
4
|
+
@font-face { font-family: "Kode Mono"; src: url("__FONTS__/KodeMono-Medium.ttf") format("truetype"); font-weight: 500; }
|
|
5
|
+
@font-face { font-family: "Kode Mono"; src: url("__FONTS__/KodeMono-SemiBold.ttf") format("truetype"); font-weight: 600; }
|
|
6
|
+
@font-face { font-family: "Kode Mono"; src: url("__FONTS__/KodeMono-Bold.ttf") format("truetype"); font-weight: 700; }
|
|
7
|
+
/* display text: the Bold file under every weight, so it is bold whatever weight a rule asks for */
|
|
8
|
+
@font-face { font-family: "Kode Mono Bold"; src: url("__FONTS__/KodeMono-Bold.ttf") format("truetype"); font-weight: 100 900; }
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
--ink: #1b1b1b;
|
|
12
|
+
--muted: #6f6f6f;
|
|
13
|
+
--faint: #a3a3a3;
|
|
14
|
+
--rule: #d6d6d6;
|
|
15
|
+
--tint: #f3f3f3;
|
|
16
|
+
--accent: #ff5c00; /* oklch(0.6837 0.2120 40.59) */
|
|
17
|
+
--accent-ink: #d94e00; /* accent for small text on white */
|
|
18
|
+
--accent-tint: #fff1e8;
|
|
19
|
+
--danger: #c62828;
|
|
20
|
+
--dark: #111111;
|
|
21
|
+
--mono: "Kode Mono", "JetBrains Mono", "DejaVu Sans Mono", monospace;
|
|
22
|
+
--display: "Kode Mono Bold", "Kode Mono", monospace;
|
|
23
|
+
}
|
|
24
|
+
@page { size: A4; margin: 0; }
|
|
25
|
+
* { box-sizing: border-box; }
|
|
26
|
+
html, body { margin: 0; padding: 0; background: #e9e9e9; }
|
|
27
|
+
body { font-family: var(--mono); font-size: 7.6pt; line-height: 1.5; color: var(--ink); -webkit-print-color-adjust: exact; print-color-adjust: exact; font-variant-numeric: tabular-nums; }
|
|
28
|
+
#pages { display: flex; flex-direction: column; align-items: center; gap: 8mm; padding: 8mm 0; }
|
|
29
|
+
#flow { width: 178mm; }
|
|
30
|
+
|
|
31
|
+
/* ---- page frame ---------------------------------------------------------- */
|
|
32
|
+
.page { width: 210mm; height: 297mm; position: relative; overflow: hidden; background: #fff; box-shadow: 0 1px 6px rgba(0,0,0,.18); break-after: page; }
|
|
33
|
+
.pg-head { position: absolute; top: 9mm; left: 16mm; right: 16mm; height: 6mm; display: flex; align-items: baseline; gap: 3mm; border-bottom: 0.5pt solid var(--ink); font-size: 6.4pt; letter-spacing: .04em; text-transform: uppercase; }
|
|
34
|
+
.pg-mark { width: 2.2mm; height: 2.2mm; background: var(--accent); align-self: center; flex: none; }
|
|
35
|
+
.pg-title { font-family: var(--display); font-size: 7.6pt; letter-spacing: .02em; }
|
|
36
|
+
.pg-type { color: var(--muted); }
|
|
37
|
+
.pg-id { margin-left: auto; color: var(--muted); }
|
|
38
|
+
.pg-body { position: absolute; top: 20mm; left: 16mm; right: 16mm; bottom: 17mm; overflow: hidden; }
|
|
39
|
+
.pg-foot { position: absolute; bottom: 8mm; left: 16mm; right: 16mm; display: flex; align-items: baseline; gap: 4mm; border-top: 0.35pt solid var(--rule); padding-top: 1.6mm; font-size: 6.4pt; color: var(--muted); letter-spacing: .03em; }
|
|
40
|
+
.pg-brand { font-family: var(--display); color: var(--ink); letter-spacing: .06em; }
|
|
41
|
+
.pg-brand b { color: var(--accent); font-weight: 400; }
|
|
42
|
+
.pg-num { margin-left: auto; color: var(--ink); font-weight: 600; }
|
|
43
|
+
.page.overflow { outline: 2mm solid red; }
|
|
44
|
+
|
|
45
|
+
/* ---- cover ---------------------------------------------------------------- */
|
|
46
|
+
.page.cover .pg-head, .page.cover .pg-foot { display: none; }
|
|
47
|
+
.page.cover .pg-body { top: 0; left: 0; right: 0; bottom: 0; }
|
|
48
|
+
.cover-top { position: absolute; inset: 0 0 auto 0; height: 178mm; background: var(--dark); color: #fff; overflow: hidden; }
|
|
49
|
+
.cover-top .grid-bg { position: absolute; inset: 0; background-image: linear-gradient(rgba(255,255,255,.045) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.045) 1px, transparent 1px); background-size: 8mm 8mm; }
|
|
50
|
+
.cover-brand { position: absolute; top: 13mm; left: 16mm; font-family: var(--display); font-size: 8.6pt; letter-spacing: .12em; }
|
|
51
|
+
.cover-brand b { color: var(--accent); font-weight: 400; }
|
|
52
|
+
.cover-doctype { position: absolute; top: 13mm; right: 16mm; font-size: 6.9pt; letter-spacing: .14em; text-transform: uppercase; color: #bdbdbd; }
|
|
53
|
+
.cover-title { position: absolute; top: 27mm; left: 16mm; right: 16mm; font-family: var(--display); font-size: 25pt; line-height: 1.08; letter-spacing: .02em; }
|
|
54
|
+
.cover-title .accent { color: var(--accent); }
|
|
55
|
+
.cover-sub { position: absolute; top: 49mm; left: 16mm; width: 112mm; font-size: 7.6pt; line-height: 1.55; color: #cfcfcf; }
|
|
56
|
+
.cover-hero { position: absolute; left: 6mm; right: 6mm; top: 64mm; bottom: 4mm; }
|
|
57
|
+
.cover-hero img { width: 100%; height: 100%; object-fit: contain; }
|
|
58
|
+
.cover-rev { position: absolute; top: 13mm; right: 16mm; margin-top: 6mm; display: flex; gap: 0; font-size: 6.4pt; letter-spacing: .06em; }
|
|
59
|
+
.cover-rev span { border: 0.5pt solid #7a7a7a; padding: .6mm 2mm; color: #e0e0e0; }
|
|
60
|
+
.cover-rev span:first-child { background: var(--accent); border-color: var(--accent); color: #111; font-weight: 700; }
|
|
61
|
+
.cover-bottom { position: absolute; top: 186mm; left: 16mm; right: 16mm; bottom: 12mm; }
|
|
62
|
+
.cover-foot { position: absolute; left: 16mm; right: 16mm; bottom: 9mm; display: flex; gap: 4mm; font-size: 6.4pt; color: var(--muted); border-top: 0.35pt solid var(--rule); padding-top: 1.6mm; }
|
|
63
|
+
.cover-foot .pg-brand { color: var(--ink); }
|
|
64
|
+
.cover-foot .right { margin-left: auto; }
|
|
65
|
+
|
|
66
|
+
/* ---- headings ------------------------------------------------------------- */
|
|
67
|
+
h1.sec { font-family: var(--display); font-weight: 400; font-size: 13.5pt; line-height: 1.15; letter-spacing: .02em; margin: 0 0 3.5mm; padding-bottom: 1.8mm; border-bottom: 0.5pt solid var(--ink); display: flex; align-items: baseline; gap: 3mm; }
|
|
68
|
+
h1.sec .num { color: var(--accent); font-size: 8.6pt; letter-spacing: .06em; }
|
|
69
|
+
h1.sec .aside { margin-left: auto; font-family: var(--mono); font-size: 6.4pt; color: var(--muted); letter-spacing: .04em; text-transform: uppercase; }
|
|
70
|
+
h2.sub { font-size: 7.6pt; font-weight: 600; text-transform: uppercase; letter-spacing: .09em; margin: 5mm 0 2mm; display: flex; align-items: center; gap: 2mm; }
|
|
71
|
+
h2.sub::before { content: ""; width: 1.6mm; height: 1.6mm; background: var(--accent); flex: none; }
|
|
72
|
+
.pg-body > h2.sub:first-child, .pg-body > .sec + h2.sub { margin-top: 1mm; }
|
|
73
|
+
p { margin: 0 0 2.2mm; }
|
|
74
|
+
.prose { max-width: 128mm; }
|
|
75
|
+
.lead { font-size: 8.6pt; line-height: 1.55; max-width: 150mm; margin-bottom: 4mm; }
|
|
76
|
+
.muted { color: var(--muted); }
|
|
77
|
+
.small { font-size: 6.4pt; }
|
|
78
|
+
code, .code { font-family: var(--mono); font-size: 6.9pt; background: var(--tint); padding: 0 .6mm; }
|
|
79
|
+
.gap-block { margin-bottom: 5mm; }
|
|
80
|
+
|
|
81
|
+
/* ---- values and markers ---------------------------------------------------- */
|
|
82
|
+
.v { white-space: nowrap; }
|
|
83
|
+
.v.st-gap { color: var(--faint); }
|
|
84
|
+
sup.mk { font-size: 5.5pt; font-weight: 700; color: var(--accent-ink); margin-left: .35mm; vertical-align: 2.2pt; line-height: 0; letter-spacing: 0; }
|
|
85
|
+
sup.mk-gap { color: var(--faint); }
|
|
86
|
+
.legend { display: flex; flex-wrap: wrap; gap: 1.2mm 5mm; font-size: 6.4pt; color: var(--muted); border-top: 0.35pt solid var(--rule); padding-top: 1.8mm; margin-top: 3mm; }
|
|
87
|
+
|
|
88
|
+
/* ---- layout helpers -------------------------------------------------------- */
|
|
89
|
+
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 7mm; }
|
|
90
|
+
.cols-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 5mm; }
|
|
91
|
+
.cols-60 { display: grid; grid-template-columns: 1.45fr 1fr; gap: 7mm; }
|
|
92
|
+
.block { margin-bottom: 5mm; }
|
|
93
|
+
|
|
94
|
+
/* ---- key figures ------------------------------------------------------------ */
|
|
95
|
+
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 0.5pt solid var(--ink); border-bottom: 0.5pt solid var(--ink); }
|
|
96
|
+
.kpi { padding: 2.6mm 3mm 2.8mm 0; border-right: 0.35pt solid var(--rule); padding-left: 3mm; }
|
|
97
|
+
.kpi:first-child { padding-left: 0; }
|
|
98
|
+
.kpi:last-child { border-right: 0; }
|
|
99
|
+
.kpi .k-label { font-size: 5.8pt; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
|
|
100
|
+
.kpi .k-value { font-family: var(--display); font-size: 15pt; line-height: 1.25; margin-top: 1mm; white-space: nowrap; }
|
|
101
|
+
.kpi .k-value .v { font-family: var(--display); }
|
|
102
|
+
.kpi .k-sub { font-size: 5.8pt; color: var(--muted); line-height: 1.35; margin-top: .6mm; }
|
|
103
|
+
.kpis.k6 { grid-template-columns: repeat(3, 1fr); }
|
|
104
|
+
.kpis.k6 .kpi:nth-child(3n+1) { padding-left: 0; }
|
|
105
|
+
.kpis.k6 .kpi:nth-child(3n) { border-right: 0; }
|
|
106
|
+
.kpis.k6 .kpi:nth-child(n+4) { border-top: 0.35pt solid var(--rule); }
|
|
107
|
+
|
|
108
|
+
/* ---- tables ------------------------------------------------------------------ */
|
|
109
|
+
table { border-collapse: collapse; width: 100%; font-size: 6.9pt; line-height: 1.38; }
|
|
110
|
+
th { text-align: left; font-weight: 600; background: var(--tint); padding: 1.3mm 1.8mm; border-bottom: 0.5pt solid var(--ink); font-size: 6.4pt; letter-spacing: .05em; text-transform: uppercase; white-space: nowrap; }
|
|
111
|
+
td { padding: 1.15mm 1.8mm; border-bottom: 0.25pt solid var(--rule); vertical-align: top; }
|
|
112
|
+
td.num, th.num { text-align: right; }
|
|
113
|
+
td.unit { color: var(--muted); white-space: nowrap; }
|
|
114
|
+
td.key { white-space: nowrap; }
|
|
115
|
+
td.wrap { word-break: break-word; }
|
|
116
|
+
tr.group td { font-weight: 600; border-bottom: 0.5pt solid var(--ink); padding-top: 2.4mm; background: none; text-transform: uppercase; letter-spacing: .07em; font-size: 6.4pt; }
|
|
117
|
+
tr.group:first-child td { padding-top: 1.3mm; }
|
|
118
|
+
table.pinout td:first-child { font-weight: 600; }
|
|
119
|
+
table.dense td { padding: .8mm 1.6mm; }
|
|
120
|
+
.continued > .cont-note, .cont-note { display: none; }
|
|
121
|
+
.continued .cont-note { display: block; font-size: 6.4pt; color: var(--muted); margin-bottom: 1mm; text-transform: uppercase; letter-spacing: .08em; }
|
|
122
|
+
.fn { font-size: 6.4pt; color: var(--muted); margin-top: 1.4mm; }
|
|
123
|
+
.tag { display: inline-block; font-size: 5.8pt; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; padding: .1mm 1.1mm; border: 0.4pt solid var(--faint); color: var(--muted); border-radius: .6mm; white-space: nowrap; }
|
|
124
|
+
.tag.ok { border-color: #2e7d32; color: #2e7d32; }
|
|
125
|
+
.tag.warn { border-color: var(--accent-ink); color: var(--accent-ink); }
|
|
126
|
+
.tag.err { border-color: var(--danger); color: var(--danger); }
|
|
127
|
+
.tag.info { border-color: var(--faint); color: var(--muted); }
|
|
128
|
+
|
|
129
|
+
/* ---- figures ------------------------------------------------------------------ */
|
|
130
|
+
figure { margin: 0 0 4mm; }
|
|
131
|
+
.figwrap { position: relative; width: 100%; }
|
|
132
|
+
.figwrap img { display: block; width: 100%; height: auto; }
|
|
133
|
+
.figwrap svg.overlay { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
|
|
134
|
+
figcaption, .cap { font-size: 6.4pt; color: var(--muted); margin-top: 1.6mm; display: flex; gap: 2mm; }
|
|
135
|
+
figcaption b, .cap b { color: var(--ink); font-weight: 600; white-space: nowrap; }
|
|
136
|
+
figcaption .unit { margin-left: auto; white-space: nowrap; }
|
|
137
|
+
.chart { display: block; width: 100%; height: auto; }
|
|
138
|
+
.chart text { font-family: var(--mono); }
|
|
139
|
+
.chartbox { border-top: 0.35pt solid var(--rule); padding-top: 2mm; }
|
|
140
|
+
.chartbox h3 { font-size: 6.9pt; font-weight: 600; margin: 0 0 1mm; letter-spacing: .03em; }
|
|
141
|
+
|
|
142
|
+
/* ---- callouts (SVG) ------------------------------------------------------------ */
|
|
143
|
+
.co-leader { stroke: var(--ink); fill: none; }
|
|
144
|
+
.co-dot { fill: var(--ink); }
|
|
145
|
+
.co-bubble { fill: #fff; stroke: var(--ink); }
|
|
146
|
+
.co-bubble.acc { fill: var(--accent); stroke: var(--accent); }
|
|
147
|
+
.co-num { font-family: var(--mono); font-weight: 700; fill: var(--ink); }
|
|
148
|
+
.co-num.acc { fill: #fff; }
|
|
149
|
+
.co-label { font-family: var(--mono); fill: var(--ink); }
|
|
150
|
+
.co-label.muted { fill: var(--muted); }
|
|
151
|
+
|
|
152
|
+
/* ---- admonitions ------------------------------------------------------------------ */
|
|
153
|
+
.note { display: grid; grid-template-columns: 5.5mm 1fr; gap: 1.5mm; border-top: 0.35pt solid var(--rule); border-bottom: 0.35pt solid var(--rule); padding: 1.8mm 0; margin: 2mm 0; font-size: 6.9pt; }
|
|
154
|
+
.note + .note { border-top: 0; margin-top: -2mm; }
|
|
155
|
+
.note svg { width: 4.2mm; height: 4.2mm; margin-top: .2mm; }
|
|
156
|
+
.note b { font-weight: 700; letter-spacing: .04em; }
|
|
157
|
+
.note.warning b { color: var(--danger); }
|
|
158
|
+
.note.caution b { color: var(--accent-ink); }
|
|
159
|
+
|
|
160
|
+
/* ---- contents ------------------------------------------------------------------ */
|
|
161
|
+
ol.toc { list-style: none; padding: 0; margin: 0; }
|
|
162
|
+
ol.toc li { display: flex; align-items: baseline; gap: 2mm; padding: 1.1mm 0; border-bottom: 0.25pt solid var(--rule); }
|
|
163
|
+
.toc-n { color: var(--accent-ink); width: 7mm; flex: none; }
|
|
164
|
+
.toc-dots { flex: 1; }
|
|
165
|
+
.toc-p { font-weight: 600; }
|
|
166
|
+
|
|
167
|
+
/* ---- assembly steps -------------------------------------------------------------- */
|
|
168
|
+
.step { border-top: 0.6pt solid var(--ink); padding-top: 2.6mm; margin-bottom: 5mm; }
|
|
169
|
+
.step-head { display: flex; align-items: center; gap: 3mm; margin-bottom: 2.4mm; }
|
|
170
|
+
.step-no { width: 8.5mm; height: 8.5mm; border-radius: 50%; background: var(--accent); color: #fff; font-family: var(--display); font-size: 12pt; display: flex; align-items: center; justify-content: center; flex: none; }
|
|
171
|
+
.step-head h2 { font-family: var(--display); font-weight: 400; font-size: 12pt; margin: 0; letter-spacing: .02em; }
|
|
172
|
+
.step-head .trace { margin-left: auto; font-size: 5.8pt; color: var(--faint); text-align: right; max-width: 60mm; line-height: 1.35; }
|
|
173
|
+
.parts { display: flex; flex-wrap: wrap; gap: 2mm; margin-bottom: 2mm; }
|
|
174
|
+
.part { display: grid; grid-template-columns: 11mm auto; grid-template-rows: auto auto; column-gap: 1.8mm; align-items: center; border: 0.35pt solid var(--rule); padding: 1.2mm 2.4mm 1.2mm 1.4mm; min-width: 36mm; }
|
|
175
|
+
.part img { grid-row: span 2; width: 11mm; height: 11mm; object-fit: contain; }
|
|
176
|
+
.part .p-name { font-size: 6.4pt; line-height: 1.25; }
|
|
177
|
+
.part .p-qty { font-family: var(--display); font-size: 8.6pt; color: var(--accent-ink); line-height: 1.1; display: flex; align-items: center; gap: 1.4mm; }
|
|
178
|
+
.part .p-qty .letter { font-family: var(--mono); font-weight: 700; font-size: 5.8pt; width: 3.6mm; height: 3.6mm; border-radius: 50%; border: 0.5pt solid var(--ink); color: var(--ink); display: inline-flex; align-items: center; justify-content: center; }
|
|
179
|
+
.part.tool { border-style: dashed; }
|
|
180
|
+
.step-body { display: grid; grid-template-columns: 1fr; gap: 2mm; }
|
|
181
|
+
ol.actions { margin: 0; padding: 0; list-style: none; counter-reset: a; }
|
|
182
|
+
ol.actions li { counter-increment: a; position: relative; padding: .9mm 0 .9mm 5mm; border-bottom: 0.25pt solid var(--rule); }
|
|
183
|
+
ol.actions li::before { content: counter(a) "."; position: absolute; left: 0; font-weight: 700; color: var(--ink); }
|
|
184
|
+
ol.actions li:last-child { border-bottom: 0; }
|
|
185
|
+
.step-meta { display: flex; flex-wrap: wrap; gap: 1mm 5mm; font-size: 6.4pt; color: var(--muted); margin-top: 1.2mm; }
|
|
186
|
+
.step-meta b { color: var(--ink); font-weight: 600; }
|
|
187
|
+
.wiretable td { font-size: 6.4pt; }
|
|
188
|
+
.x-flag { color: var(--accent-ink); font-weight: 700; }
|
|
189
|
+
|
|
190
|
+
/* ---- BOM thumbnails ---------------------------------------------------------------- */
|
|
191
|
+
.kit { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0; border-top: 0.5pt solid var(--ink); }
|
|
192
|
+
.kit-item { border-bottom: 0.35pt solid var(--rule); border-right: 0.35pt solid var(--rule); padding: 2mm 2mm 2.2mm; display: flex; flex-direction: column; gap: .8mm; min-height: 30mm; }
|
|
193
|
+
.kit-item:nth-child(5n) { border-right: 0; }
|
|
194
|
+
.kit-item img { width: 100%; height: 16mm; object-fit: contain; }
|
|
195
|
+
.kit-item .k-name { font-size: 5.8pt; line-height: 1.3; }
|
|
196
|
+
.kit-item .k-qty { font-family: var(--display); color: var(--accent-ink); font-size: 8.6pt; }
|
|
197
|
+
.kit-item .k-id { font-size: 5.8pt; color: var(--faint); word-break: break-all; }
|
|
198
|
+
.kit-item.noimg img { display: none; }
|
|
199
|
+
.kit-item .ph { height: 16mm; display: flex; align-items: center; justify-content: center; color: var(--faint); font-size: 5.8pt; border: 0.35pt dashed var(--rule); }
|
|
200
|
+
|
|
201
|
+
/* ---- additions ---------------------------------------------------------------------- */
|
|
202
|
+
.v.wrap { white-space: normal; }
|
|
203
|
+
.cover-kicker { position: absolute; top: 25mm; left: 16mm; font-size: 6.9pt; letter-spacing: .18em; text-transform: uppercase; color: var(--accent); font-weight: 600; }
|
|
204
|
+
.cover-title { top: 31mm; }
|
|
205
|
+
.cover-sub { top: 52mm; }
|
|
206
|
+
.cover-hero { top: 70mm; bottom: 9mm; }
|
|
207
|
+
.cover-herocap { position: absolute; right: 16mm; bottom: 4mm; font-size: 5.8pt; color: #8a8a8a; letter-spacing: .03em; }
|
|
208
|
+
.cover-grid { display: grid; grid-template-columns: 1fr 1.25fr; gap: 8mm; margin-top: 5mm; }
|
|
209
|
+
.cover-grid .legend { margin-top: 1mm; border-top: 0; padding-top: 0; flex-direction: column; gap: .8mm; }
|
|
210
|
+
.co-inline { display: inline-flex; width: 4.2mm; height: 4.2mm; border-radius: 50%; background: var(--accent); color: #fff; font-weight: 700; font-size: 6.4pt; align-items: center; justify-content: center; }
|
|
211
|
+
ul.plain, ul.tight { margin: 0 0 2mm; padding-left: 3.5mm; }
|
|
212
|
+
ul.plain li, ul.tight li { margin: .4mm 0; }
|
|
213
|
+
.keyrow { display: flex; align-items: center; gap: 2mm; font-size: 6.9pt; margin: .9mm 0; }
|
|
214
|
+
.sw { width: 7mm; height: 3.4mm; border: 0.5pt solid; display: inline-block; flex: none; }
|
|
215
|
+
.sw-ctx { background: #f5f5f5; border-color: #a8a8a8; }
|
|
216
|
+
.sw-new { background: #e8eaed; border-color: #242424; }
|
|
217
|
+
.sw-acc { background: #ffb584; border-color: #d24a00; }
|
|
218
|
+
.sw-guide { border: 0; height: 0; border-top: 0.9pt dashed var(--accent); }
|
|
219
|
+
.stackfig { border-top: 0.35pt solid var(--rule); padding-top: 1.6mm; }
|
|
220
|
+
.st-title { font-size: 6.9pt; font-weight: 600; margin-bottom: 1mm; }
|
|
221
|
+
.st-legend { font-size: 5.8pt; line-height: 1.4; color: var(--ink); }
|
|
222
|
+
.st-legend .mm { color: var(--accent-ink); display: inline-block; min-width: 15mm; }
|
|
223
|
+
.part .ph-s { grid-row: span 2; width: 11mm; height: 11mm; border: 0.35pt dashed var(--rule); }
|
|
224
|
+
.part .p-qty .letter.acc { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
225
|
+
.mk-inline { font-size: 5.5pt; font-weight: 700; color: var(--accent-ink); }
|
|
226
|
+
code.q { font-size: 5.8pt; background: none; padding: 0; color: var(--muted); word-break: break-all; }
|
|
227
|
+
.wiringfig svg { display: block; margin-bottom: 2mm; }
|
|
228
|
+
table.provtable { table-layout: fixed; }
|
|
229
|
+
table.provtable th:nth-child(1) { width: 20mm; } table.provtable th:nth-child(2) { width: 17mm; } table.provtable th:nth-child(3) { width: 62mm; }
|
|
230
|
+
.sysdiag { display: block; margin: 1mm 0 0; }
|
|
231
|
+
|
|
232
|
+
.step-top { break-inside: avoid; }
|
|
233
|
+
.step-cont { margin-bottom: 1.5mm; }
|
|
234
|
+
.step-end { margin-bottom: 6mm; }
|
|
235
|
+
.wiretable td:nth-child(2), .wiretable td:nth-child(3) { line-height: 1.45; }
|
|
236
|
+
.chartbox .cap { margin-bottom: 1mm; }
|
|
237
|
+
.legend-row { display: flex; gap: 4mm; font-size: 6.4pt; color: var(--muted); margin: -0.5mm 0 1mm; }
|
|
238
|
+
.legend-row i { display: inline-block; width: 5mm; height: 0; border-top: 1.2pt solid; vertical-align: middle; margin-right: 1.2mm; }
|
|
239
|
+
.legend-row i.dash { border-top-style: dashed; }
|
|
240
|
+
/* print: last so it wins over the screen rules above */
|
|
241
|
+
@media print { html, body { background: #fff; } #pages { display: block; gap: 0; padding: 0; } .page { box-shadow: none !important; margin: 0; } }
|
|
242
|
+
.accent-q { font-family: var(--display); color: var(--accent-ink); }
|
|
243
|
+
.stackfig svg { display: block; }
|
|
244
|
+
.hwtable td:first-child { width: 62mm; } .hwtable td:last-child { width: 16mm; white-space: nowrap; }
|
|
245
|
+
.kpi .k-value .u { font-family: var(--mono); font-size: 8.6pt; letter-spacing: 0; margin-left: .4mm; }
|
|
246
|
+
.cover-foot { white-space: nowrap; }
|
|
247
|
+
.cover-foot > span:nth-child(2) { overflow: hidden; text-overflow: ellipsis; }
|