Rack: brush panel holds up at rack size
Lint / Python (flake8) (push) Successful in 1m2s
Lint / JS (eslint) (push) Successful in 10s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Python (flake8) (pull_request) Successful in 1m26s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (push) Successful in 1m55s
Test / Python Tests (pytest) (push) Successful in 1m24s
Security / Python Security (bandit) (pull_request) Successful in 49s
Test / Python Tests (pytest) (pull_request) Successful in 1m10s

The bristles were hundreds of hard strands, which aliased into flat
stripes at normal rack size. Each row is now shaded root to tip with a
faint strand texture over it, so the panel looks the same at any size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-11 01:56:24 -05:00
co-authored by Claude Opus 5.5
parent 6e6150f9b9
commit 36cbdd061d
2 changed files with 17 additions and 6 deletions
+15 -5
View File
@@ -120,17 +120,25 @@ const RACK_FRONTS = {
'uacc-brush-1u': {
label: 'UniFi Rack Panel Brush 1U (UACC-Rack-Panel-Brush-1U)', units: 1, width: 1100, height: 105,
draw() {
// Bristle tufts: thin strands of varying shade, deterministic so the drawing never flickers
const strands = (y0, y1, seed) => Array.from({ length: 300 }, (_, i) => {
// Bristles: each row is shaded root to tip (dark where it leaves the frame, lighter where the
// two rows meet), with a faint strand texture on top. Strong individual strands alias into hard
// stripes at rack size, so the texture stays low-contrast and the shading carries the look.
const strands = (y0, y1, seed) => Array.from({ length: 160 }, (_, i) => {
const n = (i * 7919 + seed * 104729) % 97;
return `<rect x="${94 + i * 3.04}" y="${y0 + (n % 3)}" width="${1.2 + (n % 4) * 0.35}" height="${y1 - y0 - (n % 3)}"
style="fill:${['#1b1d20', '#23262a', '#2c2f33', '#151719'][n % 4]}"/>`;
return `<rect x="${94 + i * 5.7 + (n % 5) * 0.4}" y="${y0 + (n % 4)}" width="${1.4 + (n % 3) * 0.5}"
height="${y1 - y0 - (n % 4)}" class="ub-strand" style="opacity:${0.18 + (n % 5) * 0.05}"/>`;
}).join('');
const slot = x => `<rect class="ua-ear-slot" x="${x - 7}" y="22" width="14" height="10" rx="5"/>
<rect class="ua-ear-slot" x="${x - 7}" y="73" width="14" height="10" rx="5"/>`;
return `<defs><linearGradient id="fp-metal" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#e4e4e6"/><stop offset=".08" stop-color="#d6d6d8"/>
<stop offset=".92" stop-color="#cfcfd2"/><stop offset="1" stop-color="#a9a9ad"/>
</linearGradient>
<linearGradient id="ub-row-top" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#08090a"/><stop offset=".7" stop-color="#1f2125"/><stop offset="1" stop-color="#34373c"/>
</linearGradient>
<linearGradient id="ub-row-bottom" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#34373c"/><stop offset=".3" stop-color="#1f2125"/><stop offset="1" stop-color="#08090a"/>
</linearGradient></defs>
<rect class="fp-body" width="1100" height="105" rx="3"/>
<rect class="ua-ear-plate" x="0" y="1" width="32" height="103" rx="3"/>
@@ -142,8 +150,10 @@ const RACK_FRONTS = {
<rect class="ub-frame-shadow" x="84" y="24" width="932" height="7" rx="3"/>
<rect class="ub-frame-bevel" x="84" y="77" width="932" height="5" rx="2.5"/>
<rect class="ub-opening" x="92" y="30" width="916" height="47" rx="2"/>
<rect x="92" y="30" width="916" height="23.5" fill="url(#ub-row-top)"/>
<rect x="92" y="53.5" width="916" height="23.5" fill="url(#ub-row-bottom)"/>
${strands(31, 53, 1)}${strands(54, 76, 2)}
<rect class="ub-seam" x="92" y="53" width="916" height="1.3"/>`;
<rect class="ub-seam" x="92" y="53" width="916" height="1"/>`;
},
},
+2 -1
View File
@@ -1646,4 +1646,5 @@ body.fp-marks-off .fp-svg .fp-mark { display: none; }
.ub-frame-shadow { fill: #8c8e93; }
.ub-frame-bevel { fill: #f1f2f3; }
.ub-opening { fill: #0d0e10; }
.ub-seam { fill: #050506; }
.ub-seam { fill: #050506; opacity: .8; }
.ub-strand { fill: #000; }