Some Linux Laptop/Desktop detection and goodbye penguin
This commit is contained in:
@@ -42,11 +42,10 @@
|
||||
const NVIDIA_LOGO_PATH =
|
||||
"M0.81 0.429V0.299c0.013 -0.001 0.026 -0.002 0.038 -0.002 0.355 -0.011 0.588 0.306 0.588 0.306S1.186 0.952 0.916 0.952c-0.036 0 -0.071 -0.006 -0.105 -0.017V0.542c0.138 0.017 0.166 0.078 0.249 0.216l0.185 -0.155s-0.135 -0.177 -0.362 -0.177c-0.024 -0.001 -0.048 0.001 -0.072 0.003m0 -0.429v0.194l0.038 -0.002c0.494 -0.017 0.816 0.405 0.816 0.405s-0.37 0.45 -0.754 0.45c-0.034 0 -0.066 -0.003 -0.099 -0.009v0.12c0.027 0.003 0.055 0.006 0.082 0.006 0.358 0 0.618 -0.183 0.869 -0.399 0.042 0.034 0.212 0.114 0.247 0.15 -0.238 0.2 -0.794 0.361 -1.11 0.361 -0.03 0 -0.059 -0.002 -0.088 -0.005v0.169h1.362V0zm0 0.935v0.102c-0.331 -0.059 -0.423 -0.404 -0.423 -0.404s0.159 -0.176 0.423 -0.205v0.112h-0.001C0.671 0.524 0.562 0.654 0.562 0.654s0.062 0.218 0.248 0.282m-0.588 -0.316s0.196 -0.29 0.589 -0.32V0.194C0.376 0.229 0 0.597 0 0.597s0.213 0.616 0.81 0.672v-0.112c-0.438 -0.054 -0.588 -0.538 -0.588 -0.538";
|
||||
|
||||
// Tux penguin paths (viewBox 0 0 100 100)
|
||||
const TUX_BODY_PATH =
|
||||
"M50 8c-8 0-14 6-14 13 0 4 2 8 5 10-8 4-16 14-16 28v12c0 4 2 7 5 9l-6 4c-2 1-3 3-3 5v3c0 2 2 4 4 4h10l4-4h22l4 4h10c2 0 4-2 4-4v-3c0-2-1-4-3-5l-6-4c3-2 5-5 5-9V59c0-14-8-24-16-28 3-2 5-6 5-10 0-7-6-13-14-13z";
|
||||
const TUX_BELLY_PATH =
|
||||
"M38 52c0-8 5-15 12-15s12 7 12 15v14c0 4-5 7-12 7s-12-3-12-7V52z";
|
||||
// Tux penguin logo path (viewBox 0 0 100 100)
|
||||
const TUX_LOGO_PATH =
|
||||
"M50 5C42 5 36 11 36 18c0 4 2 8 5 10C33 32 25 42 25 56v12c0 4 2 7 5 9l-6 4c-2 1-3 3-3 5v3c0 2 2 4 4 4h10l4-4h22l4 4h10c2 0 4-2 4-4v-3c0-2-1-4-3-5l-6-4c3-2 5-5 5-9V56c0-14-8-24-16-28 3-2 5-6 5-10 0-7-6-13-14-13z";
|
||||
const TUX_LOGO_VIEWBOX = "0 0 100 100";
|
||||
|
||||
const wireColor = "rgba(179,179,179,0.8)";
|
||||
const strokeWidth = 1.5;
|
||||
@@ -55,7 +54,8 @@
|
||||
const isSpark = $derived(
|
||||
modelLower.includes("dgx") || modelLower.includes("gx10"),
|
||||
);
|
||||
const isLinux = $derived(!isSpark && modelLower.includes("linux"));
|
||||
const isLinux = $derived(!isSpark && modelLower.startsWith("linux"));
|
||||
const isLinuxLaptop = $derived(isLinux && modelLower.includes("laptop"));
|
||||
|
||||
// ── DGX Spark dimensions ──
|
||||
const dgxW = $derived(size * 1.55);
|
||||
@@ -75,13 +75,11 @@
|
||||
const dgxClipId = $derived(`di-dgx-${uid}`);
|
||||
const dgxTextureId = $derived(`di-dgx-tex-${uid}`);
|
||||
|
||||
// ── Linux Tux dimensions ──
|
||||
const tuxSize = $derived(Math.min(size * 1.0, size * 1.1));
|
||||
const tuxScale = $derived(tuxSize / 100);
|
||||
const tuxTx = $derived(cx - (100 * tuxScale) / 2);
|
||||
const tuxTy = $derived(cy - (100 * tuxScale) / 2);
|
||||
const tuxEyeR = $derived(2.5 * tuxScale);
|
||||
const tuxBellyClipId = $derived(`di-tux-belly-${uid}`);
|
||||
// ── Linux Desktop dimensions (reuses Mac Studio proportions) ──
|
||||
const linuxDesktopClipId = $derived(`di-linux-desktop-${uid}`);
|
||||
|
||||
// ── Linux Laptop dimensions (reuses MacBook proportions) ──
|
||||
const linuxScreenClipId = $derived(`di-linux-screen-${uid}`);
|
||||
|
||||
// ── Mac Studio dimensions (same ratios as TopologyGraph) ──
|
||||
const studioW = $derived(size * 1.25);
|
||||
@@ -285,98 +283,125 @@
|
||||
font-weight="700">NVIDIA</text
|
||||
>
|
||||
</g>
|
||||
{:else if isLinux}
|
||||
<!-- Linux Tux Penguin -->
|
||||
{:else if isLinuxLaptop}
|
||||
<!-- Linux Laptop — MacBook shape with Tux logo -->
|
||||
<defs>
|
||||
<clipPath id={tuxBellyClipId}>
|
||||
<path
|
||||
d={TUX_BELLY_PATH}
|
||||
transform="translate({tuxTx}, {tuxTy}) scale({tuxScale})"
|
||||
<clipPath id={linuxScreenClipId}>
|
||||
<rect
|
||||
x={mbScreenX + mbBezel}
|
||||
y={mbY + mbBezel}
|
||||
width={mbScreenW - mbBezel * 2}
|
||||
height={mbScreenH - mbBezel * 2}
|
||||
rx="2"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<!-- Body -->
|
||||
<path
|
||||
d={TUX_BODY_PATH}
|
||||
transform="translate({tuxTx}, {tuxTy}) scale({tuxScale})"
|
||||
<rect
|
||||
x={mbScreenX}
|
||||
y={mbY}
|
||||
width={mbScreenW}
|
||||
height={mbScreenH}
|
||||
rx="3"
|
||||
fill="#1a1a1a"
|
||||
stroke={wireColor}
|
||||
stroke-width={strokeWidth / tuxScale}
|
||||
stroke-width={strokeWidth}
|
||||
/>
|
||||
|
||||
<!-- White belly -->
|
||||
<path
|
||||
d={TUX_BELLY_PATH}
|
||||
transform="translate({tuxTx}, {tuxTy}) scale({tuxScale})"
|
||||
fill="rgba(220,220,220,0.85)"
|
||||
<rect
|
||||
x={mbScreenX + mbBezel}
|
||||
y={mbY + mbBezel}
|
||||
width={mbScreenW - mbBezel * 2}
|
||||
height={mbScreenH - mbBezel * 2}
|
||||
rx="2"
|
||||
fill="#0a0a12"
|
||||
/>
|
||||
|
||||
<!-- Memory fill on belly -->
|
||||
{#if ramPercent > 0}
|
||||
{@const bellyTop = tuxTy + 37 * tuxScale}
|
||||
{@const bellyH = 36 * tuxScale}
|
||||
{@const memH = (ramPercent / 100) * bellyH}
|
||||
<rect
|
||||
x={tuxTx + 38 * tuxScale}
|
||||
y={bellyTop + bellyH - memH}
|
||||
width={24 * tuxScale}
|
||||
height={memH}
|
||||
fill="rgba(255,215,0,0.75)"
|
||||
clip-path="url(#{tuxBellyClipId})"
|
||||
x={mbScreenX + mbBezel}
|
||||
y={mbY + mbBezel + (mbMemTotalH - mbMemH)}
|
||||
width={mbScreenW - mbBezel * 2}
|
||||
height={mbMemH}
|
||||
fill="rgba(255,215,0,0.85)"
|
||||
clip-path="url(#{linuxScreenClipId})"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Eyes -->
|
||||
<circle
|
||||
cx={tuxTx + 44 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR}
|
||||
fill="white"
|
||||
/>
|
||||
<circle
|
||||
cx={tuxTx + 56 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR}
|
||||
fill="white"
|
||||
/>
|
||||
<!-- Pupils -->
|
||||
<circle
|
||||
cx={tuxTx + 44 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR * 0.5}
|
||||
fill="#1a1a1a"
|
||||
/>
|
||||
<circle
|
||||
cx={tuxTx + 56 * tuxScale}
|
||||
cy={tuxTy + 16 * tuxScale}
|
||||
r={tuxEyeR * 0.5}
|
||||
fill="#1a1a1a"
|
||||
/>
|
||||
<!-- Tux logo on screen -->
|
||||
{@const tuxH = mbScreenH * 0.35}
|
||||
{@const tuxW = tuxH}
|
||||
{@const tuxX = cx - tuxW / 2}
|
||||
{@const tuxY = mbY + mbScreenH / 2 - tuxH / 2}
|
||||
<svg x={tuxX} y={tuxY} width={tuxW} height={tuxH} viewBox={TUX_LOGO_VIEWBOX}>
|
||||
<path d={TUX_LOGO_PATH} fill="#FFFFFF" opacity="0.9" />
|
||||
</svg>
|
||||
|
||||
<!-- Beak -->
|
||||
<path
|
||||
d="M{tuxTx + 46 * tuxScale} {tuxTy + 22 * tuxScale} L{tuxTx +
|
||||
50 * tuxScale} {tuxTy + 27 * tuxScale} L{tuxTx + 54 * tuxScale} {tuxTy +
|
||||
22 * tuxScale} Z"
|
||||
fill="#E8A317"
|
||||
d="M {mbBaseTopX} {mbBaseY} L {mbBaseTopX +
|
||||
mbBaseTopW} {mbBaseY} L {mbBaseBottomX + mbBaseBottomW} {mbBaseY +
|
||||
mbBaseH} L {mbBaseBottomX} {mbBaseY + mbBaseH} Z"
|
||||
fill="#2c2c2c"
|
||||
stroke={wireColor}
|
||||
stroke-width="1"
|
||||
/>
|
||||
<rect
|
||||
x={mbKbX}
|
||||
y={mbKbY}
|
||||
width={mbKbW}
|
||||
height={mbKbH}
|
||||
fill="rgba(0,0,0,0.2)"
|
||||
rx="2"
|
||||
/>
|
||||
<rect
|
||||
x={mbTpX}
|
||||
y={mbTpY}
|
||||
width={mbTpW}
|
||||
height={mbTpH}
|
||||
fill="rgba(255,255,255,0.08)"
|
||||
rx="2"
|
||||
/>
|
||||
{:else if isLinux}
|
||||
<!-- Linux Desktop — Mac Studio shape with Tux logo -->
|
||||
<defs>
|
||||
<clipPath id={linuxDesktopClipId}>
|
||||
<rect
|
||||
x={studioX}
|
||||
y={studioY + studioTopH}
|
||||
width={studioW}
|
||||
height={studioH - studioTopH}
|
||||
rx={studioCorner - 1}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
|
||||
<!-- Feet -->
|
||||
<ellipse
|
||||
cx={tuxTx + 42 * tuxScale}
|
||||
cy={tuxTy + 94 * tuxScale}
|
||||
rx={6 * tuxScale}
|
||||
ry={2.5 * tuxScale}
|
||||
fill="#E8A317"
|
||||
/>
|
||||
<ellipse
|
||||
cx={tuxTx + 58 * tuxScale}
|
||||
cy={tuxTy + 94 * tuxScale}
|
||||
rx={6 * tuxScale}
|
||||
ry={2.5 * tuxScale}
|
||||
fill="#E8A317"
|
||||
<rect
|
||||
x={studioX}
|
||||
y={studioY}
|
||||
width={studioW}
|
||||
height={studioH}
|
||||
rx={studioCorner}
|
||||
fill="#1a1a1a"
|
||||
stroke={wireColor}
|
||||
stroke-width={strokeWidth}
|
||||
/>
|
||||
{#if ramPercent > 0}
|
||||
<rect
|
||||
x={studioX}
|
||||
y={studioY + studioTopH + (studioMemTotalH - studioMemH)}
|
||||
width={studioW}
|
||||
height={studioMemH}
|
||||
fill="rgba(255,215,0,0.75)"
|
||||
clip-path="url(#{linuxDesktopClipId})"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<!-- Tux logo centered -->
|
||||
{@const dtH = (studioH - studioTopH) * 0.55}
|
||||
{@const dtW = dtH}
|
||||
{@const dtX = cx - dtW / 2}
|
||||
{@const dtY = studioY + studioTopH + (studioH - studioTopH) / 2 - dtH / 2}
|
||||
<svg x={dtX} y={dtY} width={dtW} height={dtH} viewBox={TUX_LOGO_VIEWBOX}>
|
||||
<path d={TUX_LOGO_PATH} fill="rgba(255,255,255,0.6)" />
|
||||
</svg>
|
||||
{:else if modelLower === "mac studio" || modelLower === "mac mini"}
|
||||
<!-- Mac Studio / Mac Mini -->
|
||||
<defs>
|
||||
|
||||
@@ -988,8 +988,20 @@
|
||||
stroke-width="1.5"
|
||||
/>
|
||||
<!-- Side accents -->
|
||||
<rect x="0" y="0" width={dgxW * 0.02} height={dgxH} fill="#8a7a56" />
|
||||
<rect x={dgxW - dgxW * 0.02} y="0" width={dgxW * 0.02} height={dgxH} fill="#8a7a56" />
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width={dgxW * 0.02}
|
||||
height={dgxH}
|
||||
fill="#8a7a56"
|
||||
/>
|
||||
<rect
|
||||
x={dgxW - dgxW * 0.02}
|
||||
y="0"
|
||||
width={dgxW * 0.02}
|
||||
height={dgxH}
|
||||
fill="#8a7a56"
|
||||
/>
|
||||
<!-- Left handle -->
|
||||
<rect
|
||||
x={dgxW * 0.04}
|
||||
@@ -1025,7 +1037,8 @@
|
||||
x="2"
|
||||
y={dgxH - dgxH * (node.newPercent / 100)}
|
||||
width={dgxW - 4}
|
||||
height={dgxH * ((node.newPercent - node.currentPercent) / 100)}
|
||||
height={dgxH *
|
||||
((node.newPercent - node.currentPercent) / 100)}
|
||||
fill="#FFD700"
|
||||
filter="url(#memGlow-{filterId})"
|
||||
class="animate-pulse-slow"
|
||||
@@ -1054,16 +1067,39 @@
|
||||
<!-- Eyes -->
|
||||
<circle cx={44 * sc} cy={16 * sc} r={2.5 * sc} fill="white" />
|
||||
<circle cx={56 * sc} cy={16 * sc} r={2.5 * sc} fill="white" />
|
||||
<circle cx={44 * sc} cy={16 * sc} r={1.2 * sc} fill="#1a1a1a" />
|
||||
<circle cx={56 * sc} cy={16 * sc} r={1.2 * sc} fill="#1a1a1a" />
|
||||
<circle
|
||||
cx={44 * sc}
|
||||
cy={16 * sc}
|
||||
r={1.2 * sc}
|
||||
fill="#1a1a1a"
|
||||
/>
|
||||
<circle
|
||||
cx={56 * sc}
|
||||
cy={16 * sc}
|
||||
r={1.2 * sc}
|
||||
fill="#1a1a1a"
|
||||
/>
|
||||
<!-- Beak -->
|
||||
<path
|
||||
d="M{46 * sc} {22 * sc} L{50 * sc} {27 * sc} L{54 * sc} {22 * sc} Z"
|
||||
d="M{46 * sc} {22 * sc} L{50 * sc} {27 * sc} L{54 *
|
||||
sc} {22 * sc} Z"
|
||||
fill="#E8A317"
|
||||
/>
|
||||
<!-- Feet -->
|
||||
<ellipse cx={42 * sc} cy={94 * sc} rx={6 * sc} ry={2.5 * sc} fill="#E8A317" />
|
||||
<ellipse cx={58 * sc} cy={94 * sc} rx={6 * sc} ry={2.5 * sc} fill="#E8A317" />
|
||||
<ellipse
|
||||
cx={42 * sc}
|
||||
cy={94 * sc}
|
||||
rx={6 * sc}
|
||||
ry={2.5 * sc}
|
||||
fill="#E8A317"
|
||||
/>
|
||||
<ellipse
|
||||
cx={58 * sc}
|
||||
cy={94 * sc}
|
||||
rx={6 * sc}
|
||||
ry={2.5 * sc}
|
||||
fill="#E8A317"
|
||||
/>
|
||||
</g>
|
||||
{:else}
|
||||
<!-- Unknown device - hexagon -->
|
||||
|
||||
@@ -121,12 +121,11 @@
|
||||
const NVIDIA_LOGO_PATH =
|
||||
"M0.81 0.429V0.299c0.013 -0.001 0.026 -0.002 0.038 -0.002 0.355 -0.011 0.588 0.306 0.588 0.306S1.186 0.952 0.916 0.952c-0.036 0 -0.071 -0.006 -0.105 -0.017V0.542c0.138 0.017 0.166 0.078 0.249 0.216l0.185 -0.155s-0.135 -0.177 -0.362 -0.177c-0.024 -0.001 -0.048 0.001 -0.072 0.003m0 -0.429v0.194l0.038 -0.002c0.494 -0.017 0.816 0.405 0.816 0.405s-0.37 0.45 -0.754 0.45c-0.034 0 -0.066 -0.003 -0.099 -0.009v0.12c0.027 0.003 0.055 0.006 0.082 0.006 0.358 0 0.618 -0.183 0.869 -0.399 0.042 0.034 0.212 0.114 0.247 0.15 -0.238 0.2 -0.794 0.361 -1.11 0.361 -0.03 0 -0.059 -0.002 -0.088 -0.005v0.169h1.362V0zm0 0.935v0.102c-0.331 -0.059 -0.423 -0.404 -0.423 -0.404s0.159 -0.176 0.423 -0.205v0.112h-0.001C0.671 0.524 0.562 0.654 0.562 0.654s0.062 0.218 0.248 0.282m-0.588 -0.316s0.196 -0.29 0.589 -0.32V0.194C0.376 0.229 0 0.597 0 0.597s0.213 0.616 0.81 0.672v-0.112c-0.438 -0.054 -0.588 -0.538 -0.588 -0.538";
|
||||
|
||||
// Tux penguin SVG path — simplified silhouette for topology view
|
||||
// viewBox: 0 0 100 100
|
||||
const TUX_BODY_PATH =
|
||||
"M50 8c-8 0-14 6-14 13 0 4 2 8 5 10-8 4-16 14-16 28v12c0 4 2 7 5 9l-6 4c-2 1-3 3-3 5v3c0 2 2 4 4 4h10l4-4h22l4 4h10c2 0 4-2 4-4v-3c0-2-1-4-3-5l-6-4c3-2 5-5 5-9V59c0-14-8-24-16-28 3-2 5-6 5-10 0-7-6-13-14-13z";
|
||||
const TUX_BELLY_PATH =
|
||||
"M38 52c0-8 5-15 12-15s12 7 12 15v14c0 4-5 7-12 7s-12-3-12-7V52z";
|
||||
// Tux penguin logo — simplified silhouette suitable as a logo overlay
|
||||
// viewBox: 0 0 100 120
|
||||
const TUX_LOGO_PATH =
|
||||
"M50 5C42 5 36 11 36 18c0 4 2 8 5 10C33 32 25 42 25 56v12c0 4 2 7 5 9l-6 4c-2 1-3 3-3 5v3c0 2 2 4 4 4h10l4-4h22l4 4h10c2 0 4-2 4-4v-3c0-2-1-4-3-5l-6-4c3-2 5-5 5-9V56c0-14-8-24-16-28 3-2 5-6 5-10 0-7-6-13-14-13z";
|
||||
const TUX_LOGO_VIEWBOX = "0 0 100 100";
|
||||
|
||||
function formatBytes(bytes: number, decimals = 1): string {
|
||||
if (!bytes || bytes === 0) return "0B";
|
||||
@@ -567,10 +566,11 @@
|
||||
const modelLower = modelId.toLowerCase();
|
||||
const identity = identitiesData[nodeInfo.id];
|
||||
const nameLower = (friendlyName || "").toLowerCase();
|
||||
const isSpark =
|
||||
modelLower.includes("dgx") || modelLower.includes("gx10");
|
||||
const isSpark = modelLower.includes("dgx") || modelLower.includes("gx10");
|
||||
const isLinux =
|
||||
!isSpark && (modelLower === "linux" || identity?.osVersion === "Linux");
|
||||
!isSpark &&
|
||||
(modelLower.startsWith("linux") || identity?.osVersion === "Linux");
|
||||
const isLinuxLaptop = isLinux && modelLower.includes("laptop");
|
||||
|
||||
// Check node states for styling
|
||||
const isHighlighted = highlightedNodes.has(nodeInfo.id);
|
||||
@@ -826,107 +826,202 @@
|
||||
.attr("font-family", "monospace")
|
||||
.attr("font-weight", "700")
|
||||
.text("NVIDIA");
|
||||
} else if (isLinux) {
|
||||
// Linux — Tux penguin
|
||||
iconBaseWidth = nodeRadius * 1.0;
|
||||
iconBaseHeight = nodeRadius * 1.1;
|
||||
const tuxSize = Math.min(iconBaseWidth, iconBaseHeight);
|
||||
const scale = tuxSize / 100;
|
||||
const tx = nodeInfo.x - (100 * scale) / 2;
|
||||
const ty = nodeInfo.y - (100 * scale) / 2;
|
||||
} else if (isLinuxLaptop) {
|
||||
// Linux Laptop — same shape as MacBook but with Tux logo
|
||||
iconBaseWidth = nodeRadius * 1.6;
|
||||
iconBaseHeight = nodeRadius * 1.15;
|
||||
const x = nodeInfo.x - iconBaseWidth / 2;
|
||||
const y = nodeInfo.y - iconBaseHeight / 2;
|
||||
|
||||
// Body (dark outline)
|
||||
const screenHeight = iconBaseHeight * 0.7;
|
||||
const baseHeight = iconBaseHeight * 0.3;
|
||||
const screenWidth = iconBaseWidth * 0.85;
|
||||
const screenX = nodeInfo.x - screenWidth / 2;
|
||||
const screenBezel = 3;
|
||||
|
||||
const linuxScreenClipId = `linux-screen-${nodeInfo.id.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
||||
defs
|
||||
.append("clipPath")
|
||||
.attr("id", linuxScreenClipId)
|
||||
.append("rect")
|
||||
.attr("x", screenX + screenBezel)
|
||||
.attr("y", y + screenBezel)
|
||||
.attr("width", screenWidth - screenBezel * 2)
|
||||
.attr("height", screenHeight - screenBezel * 2)
|
||||
.attr("rx", 2);
|
||||
|
||||
// Screen outer frame
|
||||
nodeG
|
||||
.append("path")
|
||||
.append("rect")
|
||||
.attr("class", "node-outline")
|
||||
.attr("d", TUX_BODY_PATH)
|
||||
.attr("transform", `translate(${tx}, ${ty}) scale(${scale})`)
|
||||
.attr("x", screenX)
|
||||
.attr("y", y)
|
||||
.attr("width", screenWidth)
|
||||
.attr("height", screenHeight)
|
||||
.attr("rx", 3)
|
||||
.attr("fill", "#1a1a1a")
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", strokeWidth / scale);
|
||||
.attr("stroke-width", strokeWidth);
|
||||
|
||||
// White belly
|
||||
// Screen inner
|
||||
nodeG
|
||||
.append("path")
|
||||
.attr("d", TUX_BELLY_PATH)
|
||||
.attr("transform", `translate(${tx}, ${ty}) scale(${scale})`)
|
||||
.attr("fill", "rgba(220,220,220,0.85)");
|
||||
.append("rect")
|
||||
.attr("x", screenX + screenBezel)
|
||||
.attr("y", y + screenBezel)
|
||||
.attr("width", screenWidth - screenBezel * 2)
|
||||
.attr("height", screenHeight - screenBezel * 2)
|
||||
.attr("rx", 2)
|
||||
.attr("fill", "#0a0a12");
|
||||
|
||||
// Memory fill on belly (from bottom up)
|
||||
// Memory fill on screen
|
||||
if (ramUsagePercent > 0) {
|
||||
const bellyClipId = `tux-belly-${nodeInfo.id.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
||||
defs
|
||||
.append("clipPath")
|
||||
.attr("id", bellyClipId)
|
||||
.append("path")
|
||||
.attr("d", TUX_BELLY_PATH)
|
||||
.attr("transform", `translate(${tx}, ${ty}) scale(${scale})`);
|
||||
|
||||
const bellyTop = ty + 37 * scale;
|
||||
const bellyHeight = 36 * scale;
|
||||
const memHeight = (ramUsagePercent / 100) * bellyHeight;
|
||||
const memFillTotalHeight = screenHeight - screenBezel * 2;
|
||||
const memFillActualHeight =
|
||||
(ramUsagePercent / 100) * memFillTotalHeight;
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", tx + 38 * scale)
|
||||
.attr("y", bellyTop + bellyHeight - memHeight)
|
||||
.attr("width", 24 * scale)
|
||||
.attr("height", memHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.75)")
|
||||
.attr("clip-path", `url(#${bellyClipId})`);
|
||||
.attr("x", screenX + screenBezel)
|
||||
.attr(
|
||||
"y",
|
||||
y + screenBezel + (memFillTotalHeight - memFillActualHeight),
|
||||
)
|
||||
.attr("width", screenWidth - screenBezel * 2)
|
||||
.attr("height", memFillActualHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.85)")
|
||||
.attr("clip-path", `url(#${linuxScreenClipId})`);
|
||||
}
|
||||
|
||||
// Eyes
|
||||
const eyeRadius = 2.5 * scale;
|
||||
// Tux logo on screen
|
||||
const tuxLogoH = screenHeight * 0.35;
|
||||
const tuxLogoW = tuxLogoH;
|
||||
const tuxLogoX = nodeInfo.x - tuxLogoW / 2;
|
||||
const tuxLogoY = y + screenHeight / 2 - tuxLogoH / 2;
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 44 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius)
|
||||
.attr("fill", "white");
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 56 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius)
|
||||
.attr("fill", "white");
|
||||
// Pupils
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 44 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius * 0.5)
|
||||
.attr("fill", "#1a1a1a");
|
||||
nodeG
|
||||
.append("circle")
|
||||
.attr("cx", tx + 56 * scale)
|
||||
.attr("cy", ty + 16 * scale)
|
||||
.attr("r", eyeRadius * 0.5)
|
||||
.attr("fill", "#1a1a1a");
|
||||
.append("svg")
|
||||
.attr("x", tuxLogoX)
|
||||
.attr("y", tuxLogoY)
|
||||
.attr("width", tuxLogoW)
|
||||
.attr("height", tuxLogoH)
|
||||
.attr("viewBox", TUX_LOGO_VIEWBOX)
|
||||
.append("path")
|
||||
.attr("d", TUX_LOGO_PATH)
|
||||
.attr("fill", "#FFFFFF")
|
||||
.attr("opacity", 0.9);
|
||||
|
||||
// Keyboard base (trapezoidal)
|
||||
const baseY = y + screenHeight;
|
||||
const baseTopWidth = screenWidth;
|
||||
const baseBottomWidth = iconBaseWidth;
|
||||
const baseTopX = nodeInfo.x - baseTopWidth / 2;
|
||||
const baseBottomX = nodeInfo.x - baseBottomWidth / 2;
|
||||
|
||||
// Beak
|
||||
nodeG
|
||||
.append("path")
|
||||
.attr(
|
||||
"d",
|
||||
`M${tx + 46 * scale} ${ty + 22 * scale} L${tx + 50 * scale} ${ty + 27 * scale} L${tx + 54 * scale} ${ty + 22 * scale} Z`,
|
||||
`M ${baseTopX} ${baseY} L ${baseTopX + baseTopWidth} ${baseY} L ${baseBottomX + baseBottomWidth} ${baseY + baseHeight} L ${baseBottomX} ${baseY + baseHeight} Z`,
|
||||
)
|
||||
.attr("fill", "#E8A317");
|
||||
.attr("fill", "#2c2c2c")
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", 1);
|
||||
|
||||
// Feet
|
||||
// Keyboard area
|
||||
const keyboardX = baseTopX + 6;
|
||||
const keyboardY = baseY + 3;
|
||||
const keyboardWidth = baseTopWidth - 12;
|
||||
const keyboardHeight = baseHeight * 0.55;
|
||||
nodeG
|
||||
.append("ellipse")
|
||||
.attr("cx", tx + 42 * scale)
|
||||
.attr("cy", ty + 94 * scale)
|
||||
.attr("rx", 6 * scale)
|
||||
.attr("ry", 2.5 * scale)
|
||||
.attr("fill", "#E8A317");
|
||||
.append("rect")
|
||||
.attr("x", keyboardX)
|
||||
.attr("y", keyboardY)
|
||||
.attr("width", keyboardWidth)
|
||||
.attr("height", keyboardHeight)
|
||||
.attr("fill", "rgba(0,0,0,0.2)")
|
||||
.attr("rx", 2);
|
||||
|
||||
// Trackpad
|
||||
const trackpadWidth = baseTopWidth * 0.4;
|
||||
const trackpadX = nodeInfo.x - trackpadWidth / 2;
|
||||
const trackpadY = baseY + keyboardHeight + 5;
|
||||
const trackpadHeight = baseHeight * 0.3;
|
||||
nodeG
|
||||
.append("ellipse")
|
||||
.attr("cx", tx + 58 * scale)
|
||||
.attr("cy", ty + 94 * scale)
|
||||
.attr("rx", 6 * scale)
|
||||
.attr("ry", 2.5 * scale)
|
||||
.attr("fill", "#E8A317");
|
||||
.append("rect")
|
||||
.attr("x", trackpadX)
|
||||
.attr("y", trackpadY)
|
||||
.attr("width", trackpadWidth)
|
||||
.attr("height", trackpadHeight)
|
||||
.attr("fill", "rgba(255,255,255,0.08)")
|
||||
.attr("rx", 2);
|
||||
} else if (isLinux) {
|
||||
// Linux Desktop — same shape as Mac Studio but with Tux logo
|
||||
iconBaseWidth = nodeRadius * 1.25;
|
||||
iconBaseHeight = nodeRadius * 0.85;
|
||||
const x = nodeInfo.x - iconBaseWidth / 2;
|
||||
const y = nodeInfo.y - iconBaseHeight / 2;
|
||||
const cornerRadius = 4;
|
||||
const topSurfaceHeight = iconBaseHeight * 0.15;
|
||||
|
||||
const linuxDesktopClipId = `linux-desktop-${nodeInfo.id.replace(/[^a-zA-Z0-9]/g, "-")}`;
|
||||
defs
|
||||
.append("clipPath")
|
||||
.attr("id", linuxDesktopClipId)
|
||||
.append("rect")
|
||||
.attr("x", x)
|
||||
.attr("y", y + topSurfaceHeight)
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", iconBaseHeight - topSurfaceHeight)
|
||||
.attr("rx", cornerRadius - 1);
|
||||
|
||||
// Main body
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("class", "node-outline")
|
||||
.attr("x", x)
|
||||
.attr("y", y)
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", iconBaseHeight)
|
||||
.attr("rx", cornerRadius)
|
||||
.attr("fill", "#1a1a1a")
|
||||
.attr("stroke", wireColor)
|
||||
.attr("stroke-width", strokeWidth);
|
||||
|
||||
// Memory fill
|
||||
if (ramUsagePercent > 0) {
|
||||
const memFillTotalHeight = iconBaseHeight - topSurfaceHeight;
|
||||
const memFillActualHeight =
|
||||
(ramUsagePercent / 100) * memFillTotalHeight;
|
||||
nodeG
|
||||
.append("rect")
|
||||
.attr("x", x)
|
||||
.attr(
|
||||
"y",
|
||||
y + topSurfaceHeight + (memFillTotalHeight - memFillActualHeight),
|
||||
)
|
||||
.attr("width", iconBaseWidth)
|
||||
.attr("height", memFillActualHeight)
|
||||
.attr("fill", "rgba(255,215,0,0.75)")
|
||||
.attr("clip-path", `url(#${linuxDesktopClipId})`);
|
||||
}
|
||||
|
||||
// Tux logo centered on front face
|
||||
const tuxLogoH = (iconBaseHeight - topSurfaceHeight) * 0.55;
|
||||
const tuxLogoW = tuxLogoH;
|
||||
const tuxLogoX = nodeInfo.x - tuxLogoW / 2;
|
||||
const tuxLogoY =
|
||||
y +
|
||||
topSurfaceHeight +
|
||||
(iconBaseHeight - topSurfaceHeight) / 2 -
|
||||
tuxLogoH / 2;
|
||||
nodeG
|
||||
.append("svg")
|
||||
.attr("x", tuxLogoX)
|
||||
.attr("y", tuxLogoY)
|
||||
.attr("width", tuxLogoW)
|
||||
.attr("height", tuxLogoH)
|
||||
.attr("viewBox", TUX_LOGO_VIEWBOX)
|
||||
.append("path")
|
||||
.attr("d", TUX_LOGO_PATH)
|
||||
.attr("fill", "rgba(255,255,255,0.6)");
|
||||
} else if (modelLower === "mac studio") {
|
||||
// Mac Studio - classic cube with memory fill
|
||||
iconBaseWidth = nodeRadius * 1.25;
|
||||
|
||||
@@ -170,7 +170,20 @@ async def _get_linux_model_and_chip() -> tuple[str, str]:
|
||||
pass
|
||||
return (model, chip)
|
||||
|
||||
# Generic Linux — use /proc/cpuinfo for chip
|
||||
# Generic Linux — detect laptop vs desktop via chassis_type
|
||||
# SMBIOS chassis types: 8,9,10,14,31,32 = portable/laptop
|
||||
chassis_type = _read_dmi_field("chassis_type")
|
||||
laptop_chassis_types = {"8", "9", "10", "14", "31", "32"}
|
||||
if chassis_type in laptop_chassis_types:
|
||||
model = "Linux Laptop"
|
||||
elif chassis_type is not None:
|
||||
model = "Linux Desktop"
|
||||
|
||||
# Also check for battery as a fallback laptop indicator
|
||||
if model == "Linux" and Path("/sys/class/power_supply/BAT0").exists():
|
||||
model = "Linux Laptop"
|
||||
|
||||
# Use /proc/cpuinfo for chip
|
||||
cpuinfo_path = Path("/proc/cpuinfo")
|
||||
if cpuinfo_path.exists():
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user