WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m18s

History-free snapshot of the private development repository at cbe45c5
(2026-09-09). Code, docs and public catalogue only; no plant or job
knowledge layers, no client or job names (tools/scrub.py clean). The
intro deck is left out of this snapshot because its slide images could
not be checked for job drawings.
This commit is contained in:
2026-09-09 08:32:15 -05:00
commit 8e6ee31596
406 changed files with 223435 additions and 0 deletions
+475
View File
@@ -0,0 +1,475 @@
/* WillTools shared front-end look -- Panel Analysis's new 前端 today, the
shell every future tool (Beam Analysis, Connection Analysis) and every
results page adopts next. Reference: Eriksson Wall's own input dialogs --
plain engineering software, dense but tidy. Light theme, one accent
colour, system font stack, no build step, no framework. */
:root {
--bg: #f3f4f6;
--panel-bg: #ffffff;
--border: #d7dae0;
--border-strong: #b7bcc6;
--text: #1b1e23;
--text-muted: #6b7280;
--text-faint: #9aa1ac;
--accent: #2f6fed;
--accent-dark: #1f56c9;
--accent-contrast: #ffffff;
--error: #d33333;
--error-bg: #fdecec;
--warn: #b45309;
--warn-bg: #fff7ed;
--row-stripe: #fafbfc;
--row-hover: #eef2ff;
--radius: 4px;
--focus-ring: 0 0 0 3px rgba(47, 111, 237, 0.28);
color-scheme: light;
}
* { box-sizing: border-box; }
html, body {
height: 100%;
}
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
font-size: 13px;
line-height: 1.45;
}
button, input, select, textarea {
font: inherit;
color: inherit;
}
/* --------------------------------------------------------------- layout */
.app-shell {
display: flex;
flex-direction: column;
height: 100vh;
}
.toolbar {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 14px;
background: var(--panel-bg);
border-bottom: 1px solid var(--border);
flex: 0 0 auto;
}
.toolbar-title {
font-size: 15px;
font-weight: 600;
}
.pa-mark {
font-weight: 400;
color: var(--text-muted);
margin-left: 6px;
}
.toolbar-spacer { flex: 1 1 auto; }
.btn {
appearance: none;
border: 1px solid var(--border-strong);
background: #fff;
color: var(--text);
border-radius: var(--radius);
padding: 6px 14px;
font-size: 13px;
cursor: pointer;
}
.btn:hover { background: #f4f5f7; }
.btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn-primary {
background: var(--accent);
border-color: var(--accent-dark);
color: var(--accent-contrast);
font-weight: 600;
}
.btn-primary:hover { background: var(--accent-dark); }
.banner {
flex: 0 0 auto;
background: var(--error-bg);
color: var(--error);
border-bottom: 1px solid #f3c2c2;
padding: 8px 14px;
font-size: 12.5px;
}
.banner[hidden] { display: none; }
/* Two faces, never alike: amber = input still needed (the user's to fill),
red = something broke (a bug). The bare `.banner` is red so an
unlabelled failure is loud, never calm. */
.banner.banner--input {
background: var(--warn-bg);
color: var(--warn);
border-bottom-color: #f2d9b6;
}
.banner.banner--crash {
background: var(--error-bg);
color: var(--error);
border-bottom-color: #f3c2c2;
}
.spec-problems {
flex: 0 0 auto;
background: var(--warn-bg);
color: var(--warn);
border-bottom: 1px solid #f2d9b6;
padding: 6px 14px;
font-size: 12px;
}
.tabs {
display: flex;
gap: 2px;
background: var(--panel-bg);
border-bottom: 1px solid var(--border);
padding: 0 10px;
flex: 0 0 auto;
overflow-x: auto;
}
.tab {
appearance: none;
border: none;
background: transparent;
padding: 10px 14px;
font-size: 13px;
color: var(--text-muted);
cursor: pointer;
border-bottom: 3px solid transparent;
white-space: nowrap;
}
.tab:hover { color: var(--text); }
.tab.active {
color: var(--text);
font-weight: 600;
border-bottom-color: var(--accent);
}
.pages {
flex: 1 1 auto;
overflow-y: auto;
background: var(--bg);
}
.page-panel {
display: none;
padding: 18px 20px 60px;
max-width: 1180px;
margin: 0 auto;
}
.page-panel.active { display: block; }
/* -------------------------------------------------------------- headings */
h1.pa-h, h2.pa-h, h3.pa-h {
margin: 18px 0 8px;
font-weight: 600;
color: var(--text);
}
h1.pa-h { font-size: 17px; border-bottom: 1px solid var(--border); padding-bottom: 6px; }
h2.pa-h { font-size: 14.5px; margin-top: 20px; }
h3.pa-h { font-size: 13.5px; color: var(--text-muted); }
hr.pa-rule {
border: none;
border-top: 1px solid var(--border);
margin: 14px 0;
}
.pa-space { display: block; }
/* -------------------------------------------------------------- columns */
.pa-columns {
display: flex;
gap: 22px;
align-items: flex-start;
}
.pa-col {
flex: 1 1 0;
min-width: 0;
}
/* ---------------------------------------------------------------- field */
.field {
margin-bottom: 10px;
max-width: 420px;
}
.field.full-width { max-width: none; }
.field-label-row {
display: flex;
align-items: baseline;
gap: 5px;
margin-bottom: 3px;
}
.field-label {
font-size: 12.5px;
color: var(--text-muted);
}
.field.required .field-label::after {
content: " *";
color: var(--error);
}
.field-symbol {
font-size: 11px;
color: var(--text-faint);
}
.field-input {
width: 100%;
padding: 5px 7px;
border: 1px solid var(--border-strong);
border-radius: var(--radius);
background: #fff;
}
.field-input:focus {
outline: none;
border-color: var(--accent);
box-shadow: var(--focus-ring);
}
textarea.field-input { resize: vertical; min-height: 44px; }
.field-input.has-error {
border-color: var(--error);
background: var(--error-bg);
}
.field-error-msg {
color: var(--error);
font-size: 11px;
margin-top: 2px;
}
.field-check-row {
display: flex;
align-items: center;
gap: 6px;
}
.field-check-row .field-label { margin: 0; }
/* "not used by the solve" (docs/pa-web-spec.md, unused_by_solve) --
collected only; muted so it can never pass for a live input */
.unused-by-solve .field-input,
tr.unused-by-solve .field-input { opacity: 0.55; }
.unused-by-solve .field-label,
tr.unused-by-solve td:first-child { color: var(--text-muted); }
.unused-tag {
display: inline-block;
margin-left: 6px;
padding: 0 5px;
border: 1px dashed var(--text-faint);
border-radius: 3px;
color: var(--text-muted);
font-size: 10px;
line-height: 15px;
white-space: nowrap;
}
/* row-table empty state + table-level error (issue #22) */
.table-empty-state {
color: var(--text-muted);
font-size: 12px;
font-style: italic;
margin: 4px 0;
}
.editable-table-wrap.has-error table.pa-editable {
outline: 2px solid var(--error);
background: var(--error-bg);
}
.table-error-msg {
color: var(--error);
font-size: 11px;
margin: 2px 0;
}
/* provenance */
.prov-icon {
display: inline-block;
width: 13px;
height: 13px;
line-height: 13px;
text-align: center;
border-radius: 50%;
font-size: 10px;
color: var(--accent);
cursor: help;
border: 1px solid var(--accent);
}
/* ------------------------------------------------------------ sentence */
.sentence { margin: 6px 0; color: var(--text); }
.sentence.emph { font-weight: 600; }
/* ------------------------------------------------------------------ kv */
table.kv-table { border-collapse: collapse; margin: 6px 0 12px; }
table.kv-table td { padding: 3px 10px 3px 0; vertical-align: top; font-size: 12.5px; }
table.kv-table td:first-child { color: var(--text-muted); white-space: nowrap; }
/* --------------------------------------------------------------- tables */
table.pa-table, table.pa-editable, table.pa-matrix, table.pa-lc {
border-collapse: collapse;
width: 100%;
margin: 6px 0 14px;
font-size: 12.5px;
}
table.pa-table th, table.pa-table td,
table.pa-editable th, table.pa-editable td,
table.pa-matrix th, table.pa-matrix td,
table.pa-lc th, table.pa-lc td {
border: 1px solid var(--border);
padding: 4px 6px;
text-align: left;
vertical-align: middle;
}
table.pa-table th, table.pa-editable th, table.pa-matrix th, table.pa-lc th {
background: #eef1f5;
font-weight: 600;
color: var(--text-muted);
white-space: nowrap;
}
table.pa-table tbody tr:nth-child(even),
table.pa-editable tbody tr:nth-child(even) {
background: var(--row-stripe);
}
table.pa-table td input.field-input,
table.pa-editable td input.field-input,
table.pa-editable td select.field-input,
table.pa-matrix td input.field-input,
table.pa-matrix td select.field-input,
table.pa-lc td input.field-input {
padding: 3px 5px;
min-width: 44px;
}
table.pa-matrix td.na-cell {
text-align: center;
color: var(--text-faint);
}
table.pa-matrix th, table.pa-matrix td:first-child {
white-space: normal;
}
td.row-remove { text-align: center; width: 28px; }
.row-remove-btn, .row-check {
cursor: pointer;
color: var(--text-muted);
border: none;
background: none;
font-size: 14px;
}
.row-remove-btn:hover { color: var(--error); }
.add-row-btn {
margin: 2px 0 14px;
}
/* -------------------------------------------------------------- legend */
.legend {
display: flex;
gap: 18px;
flex-wrap: wrap;
font-size: 12px;
color: var(--text-muted);
margin: 4px 0 10px;
}
.legend-group-title { font-weight: 600; margin-right: 6px; }
.legend-item { display: inline-flex; align-items: center; gap: 4px; }
.legend-glyph { font-size: 13px; }
/* ------------------------------------------------------------- drawing */
.drawing-block {
margin: 10px 0 16px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--panel-bg);
padding: 8px 10px;
}
.drawing-caption {
font-size: 12px;
color: var(--text-muted);
margin-bottom: 6px;
}
.drawing-host { width: 100%; }
.drawing-problems {
font-size: 11px;
color: var(--warn);
margin-top: 4px;
}
.drawing-problems:empty { display: none; }
/* -------------------------------------------------------------- details */
details.pa-section {
margin: 10px 0;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--panel-bg);
}
details.pa-section > summary {
cursor: pointer;
padding: 8px 10px;
font-weight: 600;
font-size: 13px;
list-style: none;
}
details.pa-section > summary::-webkit-details-marker { display: none; }
details.pa-section > summary::before {
content: "▸ ";
color: var(--text-muted);
}
details.pa-section[open] > summary::before { content: "▾ "; }
details.pa-section > .pa-section-body { padding: 4px 12px 12px; }
details.pa-notes {
margin: 20px 0 4px;
border: 1px dashed var(--border-strong);
border-radius: var(--radius);
background: #fbfbfc;
}
details.pa-notes > summary {
cursor: pointer;
padding: 7px 10px;
color: var(--text-muted);
font-size: 12.5px;
list-style: none;
}
details.pa-notes > summary::-webkit-details-marker { display: none; }
.pa-notes-body { padding: 2px 14px 12px; font-size: 12px; color: var(--text-muted); }
.pa-notes-body h4 { margin: 8px 0 4px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .03em; color: var(--text-faint); }
.pa-notes-body ul { margin: 0 0 6px; padding-left: 18px; }
.pa-notes-body li { margin-bottom: 3px; }
.unknown-block {
background: #fff3cd;
border: 1px solid #ffe08a;
border-radius: var(--radius);
padding: 6px 10px;
font-size: 12px;
margin: 6px 0;
}
#errors {
position: fixed;
left: 0; right: 0; bottom: 0;
max-height: 30vh;
overflow: auto;
margin: 0;
background: #2b0000;
color: #ffd9d9;
font-size: 11px;
padding: 8px 10px;
z-index: 999;
white-space: pre-wrap;
}
#errors[hidden] { display: none; }