feat: redesign language breakdown with dedicated languages.svg and shields badges [skip ci]

This commit is contained in:
NickiCloud 2026-09-02 21:39:39 +02:00
parent e7ffe773d7
commit 9333c28d2c
8 changed files with 295 additions and 39 deletions

View File

@ -14,6 +14,7 @@ on:
- 'heatmap.svg' - 'heatmap.svg'
- 'team-stats.svg' - 'team-stats.svg'
- 'productivity.svg' - 'productivity.svg'
- 'languages.svg'
- 'README.md' - 'README.md'
jobs: jobs:
@ -51,9 +52,9 @@ jobs:
git config --global user.email "bot@webcore-consulting.dev" git config --global user.email "bot@webcore-consulting.dev"
# Prüfen, ob sich SVG-Dateien oder README.md geändert haben # Prüfen, ob sich SVG-Dateien oder README.md geändert haben
if git status --porcelain | grep -E "(heatmap.svg|team-stats.svg|productivity.svg|README.md)"; then if git status --porcelain | grep -E "(heatmap.svg|team-stats.svg|productivity.svg|languages.svg|README.md)"; then
echo "Änderungen an Grafiken oder README.md erkannt. Committe..." echo "Änderungen an Grafiken oder README.md erkannt. Committe..."
git add heatmap.svg team-stats.svg productivity.svg README.md git add heatmap.svg team-stats.svg productivity.svg languages.svg README.md
# GANZ WICHTIG: [skip ci] verhindert Endlosschleifen in Gitea Actions! # GANZ WICHTIG: [skip ci] verhindert Endlosschleifen in Gitea Actions!
git commit -m "chore(profile): update personal & team telemetry [skip ci]" git commit -m "chore(profile): update personal & team telemetry [skip ci]"

View File

@ -12,7 +12,7 @@
<img src="https://img.shields.io/badge/Gitea_Server-Online_%E2%97%8F-2ea44f?style=flat-square&logo=gitea&logoColor=white" alt="Gitea Online" /> <img src="https://img.shields.io/badge/Gitea_Server-Online_%E2%97%8F-2ea44f?style=flat-square&logo=gitea&logoColor=white" alt="Gitea Online" />
<img src="https://img.shields.io/badge/Homelab-Debian_12_Bookworm-A81D33?style=flat-square&logo=debian&logoColor=white" alt="Debian 12" /> <img src="https://img.shields.io/badge/Homelab-Debian_12_Bookworm-A81D33?style=flat-square&logo=debian&logoColor=white" alt="Debian 12" />
<img src="https://img.shields.io/badge/CI%2FCD-Gitea_Act_Runner-609926?style=flat-square&logo=docker&logoColor=white" alt="Runner Active" /> <img src="https://img.shields.io/badge/CI%2FCD-Gitea_Act_Runner-609926?style=flat-square&logo=docker&logoColor=white" alt="Runner Active" />
<img src="https://img.shields.io/badge/Telemetry-Zuletzt_aktualisiert_02.09.2026%2019%3A31_UTC-7e3f98?style=flat-square" alt="Last Sync" /> <img src="https://img.shields.io/badge/Telemetry-Zuletzt_aktualisiert_02.09.2026%2019%3A38_UTC-7e3f98?style=flat-square" alt="Last Sync" />
</p> </p>
<!-- END_SYS_TELEMETRY --> <!-- END_SYS_TELEMETRY -->
@ -78,15 +78,20 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
</div> </div>
#### 📈 Byte-genaue Sprachverteilung (Team-Codebase): #### 📈 Byte-genaue Sprachverteilung (Team-Codebase):
<div align="center">
<img src="languages.svg" alt="Byte-genaue Sprachverteilung" width="100%" />
</div>
<!-- START_LANGUAGE_METRICS --> <!-- START_LANGUAGE_METRICS -->
| Sprache | Anteil | Code-Volumen | Verteilung | | Sprache | Technologie | Anteil | Code-Volumen | Relative Verteilung |
| :--- | :---: | :---: | :--- | | :--- | :---: | :---: | :---: | :--- |
| `C#` | **56,2%** | 1,1 MB | `███████████░░░░░░░░░` | | **C#** | ![C#](https://img.shields.io/badge/C%23-a855f7?style=flat-square&logo=csharp&logoColor=white) | **56,2%** | `1,1 MB` | `▰▰▰▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱` |
| `HTML` | **40,4%** | 797,4 KB | `████████░░░░░░░░░░░░` | | **HTML** | ![HTML](https://img.shields.io/badge/HTML-e34c26?style=flat-square&logo=html5&logoColor=white) | **40,4%** | `797,4 KB` | `▰▰▰▰▰▰▰▰▱▱▱▱▱▱▱▱▱▱▱▱` |
| `CSS` | **2,3%** | 45,7 KB | `░░░░░░░░░░░░░░░░░░░░` | | **CSS** | ![CSS](https://img.shields.io/badge/CSS-563d7c?style=flat-square&logo=css3&logoColor=white) | **2,3%** | `45,7 KB` | `▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱` |
| `JavaScript` | **0,4%** | 8,6 KB | `░░░░░░░░░░░░░░░░░░░░` | | **JavaScript** | ![JavaScript](https://img.shields.io/badge/JavaScript-f1e05a?style=flat-square&logo=javascript&logoColor=black) | **0,4%** | `8,6 KB` | `▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱` |
| `PowerShell` | **0,4%** | 7,6 KB | `░░░░░░░░░░░░░░░░░░░░` | | **PowerShell** | ![PowerShell](https://img.shields.io/badge/PowerShell-012456?style=flat-square&logo=powershell&logoColor=white) | **0,4%** | `7,6 KB` | `▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱` |
| `Shell` | **0,2%** | 3,4 KB | `░░░░░░░░░░░░░░░░░░░░` | | **Shell** | ![Shell](https://img.shields.io/badge/Shell-89e051?style=flat-square&logo=gnubash&logoColor=white) | **0,2%** | `3,4 KB` | `▰▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱▱` |
<!-- END_LANGUAGE_METRICS --> <!-- END_LANGUAGE_METRICS -->
#### 🚀 Aktive Team-Repositories: #### 🚀 Aktive Team-Repositories:
@ -109,11 +114,11 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
> *Echtzeit-Feed der letzten Aktionen und Commits über alle privaten &amp; Team-Repositories.* > *Echtzeit-Feed der letzten Aktionen und Commits über alle privaten &amp; Team-Repositories.*
<!-- START_RECENT_ACTIVITY --> <!-- START_RECENT_ACTIVITY -->
- ⚡ **feat: add productivity biorhythm clock, developer achievements and quote [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 15 Minuten* - ⚡ **fix(svg): escape xml characters in productivity badge [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 6 Minuten*
- ⚡ **feat: add live commit radar, activity feed and homelab telemetry [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 18 Minuten* - ⚡ **feat: add productivity biorhythm clock, developer achievements and quote [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 22 Minuten*
- ⚡ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 22 Minuten* - ⚡ **feat: add live commit radar, activity feed and homelab telemetry [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 26 Minuten*
- ⚡ **fix(svg): remove utf-8 bom and add xml declaration for gitea typesniffer [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 30 Minuten* - ⚡ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 29 Minuten*
- ⚡ **chore(profile): update activity heatmap & stats (22 contributions) [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 37 Minuten* - ⚡ **fix(svg): remove utf-8 bom and add xml declaration for gitea typesniffer [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 37 Minuten*
<!-- END_RECENT_ACTIVITY --> <!-- END_RECENT_ACTIVITY -->
</details> </details>
@ -129,16 +134,6 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
<img src="productivity.svg" alt="Nicki's Coding Productivity Clock" width="100%" /> <img src="productivity.svg" alt="Nicki's Coding Productivity Clock" width="100%" />
</div> </div>
#### 🏆 Entwickler-Erfolge &amp; Achievements:
<!-- START_ACHIEVEMENTS -->
| Trophy | Rang | Beschreibung | Status |
| :--- | :---: | :--- | :---: |
| 🏆 **Streak Master** | `Gold` | Mindestens 4 Tage ununterbrochen aktiv gecodet | **4 Tage** ✅ |
| 💾 **Code Sovereign** | `Diamond` | Über 1,5 MB Code in Gitea verwaltet | **1,9 MB** ✅ |
| ⚡ **Zero-Dependency** | `Master` | Reines .NET 10 BCL ohne fremde NuGet-Pakete | **100% Native** ✅ |
| 🌙 **Night-Owl Veteran** | `Elite` | Hohe nächtliche Coding-Aktivität | **21,1% Nachts** ✅ |
| 🏰 **Homelab Overlord** | `Legendary` | Self-Hosted Debian, Docker, Gitea, Mailcow | **Vollautonom** ✅ |
<!-- END_ACHIEVEMENTS -->
#### 💡 Tägliche Entwickler-Philosophie: #### 💡 Tägliche Entwickler-Philosophie:
<!-- START_RANDOM_QUOTE --> <!-- START_RANDOM_QUOTE -->

View File

@ -74,7 +74,7 @@
<text class="title" x="0" y="14">🦊 NickiCloud <tspan font-weight="400" fill="var(--accent)"></tspan> <tspan font-weight="500" font-size="12" fill="var(--text-sub)">Persönliche Aktivität</tspan></text> <text class="title" x="0" y="14">🦊 NickiCloud <tspan font-weight="400" fill="var(--accent)"></tspan> <tspan font-weight="500" font-size="12" fill="var(--text-sub)">Persönliche Aktivität</tspan></text>
<rect x="510" y="0" width="190" height="22" rx="11" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" /> <rect x="510" y="0" width="190" height="22" rx="11" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" />
<circle cx="522" cy="11" r="4" fill="var(--accent)" /> <circle cx="522" cy="11" r="4" fill="var(--accent)" />
<text class="subtitle" x="532" y="15" font-weight="600">27 Commits im letzten Jahr</text> <text class="subtitle" x="532" y="15" font-weight="600">28 Commits im letzten Jahr</text>
</g> </g>
<!-- Month Labels --> <!-- Month Labels -->
<text class="lbl-month" x="34" y="54">Sep</text> <text class="lbl-month" x="34" y="54">Sep</text>
@ -1173,7 +1173,7 @@
<title>01.09.2026: 9 Commits</title> <title>01.09.2026: 9 Commits</title>
</rect> </rect>
<rect class="cell lvl-4" x="697" y="90" width="10" height="10" rx="2" style="animation-delay: 612ms;"> <rect class="cell lvl-4" x="697" y="90" width="10" height="10" rx="2" style="animation-delay: 612ms;">
<title>02.09.2026: 11 Commits</title> <title>02.09.2026: 12 Commits</title>
</rect> </rect>
<rect class="cell lvl-0" x="697" y="103" width="10" height="10" rx="2" style="animation-delay: 612ms;"> <rect class="cell lvl-0" x="697" y="103" width="10" height="10" rx="2" style="animation-delay: 612ms;">
<title>03.09.2026: 0 Commits</title> <title>03.09.2026: 0 Commits</title>
@ -1192,7 +1192,7 @@
<g transform="translate(34, 166)"> <g transform="translate(34, 166)">
<rect width="216" height="36" rx="8" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" /> <rect width="216" height="36" rx="8" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" />
<text x="12" y="27" font-size="16">📦</text> <text x="12" y="27" font-size="16">📦</text>
<text class="stat-val" x="40" y="20">27</text> <text class="stat-val" x="40" y="20">28</text>
<text class="stat-lbl" x="40" y="33">Jahres-Beiträge</text> <text class="stat-lbl" x="40" y="33">Jahres-Beiträge</text>
</g> </g>
<g transform="translate(264, 166)"> <g transform="translate(264, 166)">

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

115
languages.svg Normal file
View File

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 740 150" width="100%" height="100%">
<defs>
<clipPath id="mainLangClip">
<rect x="34" y="50" width="672" height="14" rx="7" />
</clipPath>
</defs>
<style>
:root {
--bg: #11111b;
--card: #181825;
--border: #313244;
--text-main: #cdd6f4;
--text-sub: #a6adc8;
--text-muted: #6c7086;
--accent: #d980fa;
--metric-bg: #1e1e2e;
--metric-border: #313244;
}
@media (prefers-color-scheme: light) {
:root {
--bg: #f8fafc;
--card: #ffffff;
--border: #e2e8f0;
--text-main: #0f172a;
--text-sub: #334155;
--text-muted: #64748b;
--accent: #7e22ce;
--metric-bg: #f1f5f9;
--metric-border: #cbd5e1;
}
}
.bg-panel { fill: var(--bg); stroke: var(--border); stroke-width: 1.2px; rx: 12px; }
.title { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: 700; fill: var(--text-main); }
.subtitle { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; fill: var(--text-sub); }
.lang-name { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 600; fill: var(--text-main); }
.lang-pct { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 700; fill: var(--accent); }
.lang-size { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 10px; fill: var(--text-muted); }
</style>
<rect class="bg-panel" x="1" y="1" width="738" height="148" />
<g transform="translate(24, 18)">
<text class="title" x="0" y="14">📈 Byte-genaue Sprachverteilung <tspan font-weight="400" fill="var(--accent)"></tspan> <tspan font-weight="500" font-size="11" fill="var(--text-sub)">Team-Codebase</tspan></text>
<rect x="445" y="0" width="255" height="22" rx="11" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" />
<circle cx="457" cy="11" r="4" fill="var(--accent)" />
<text class="subtitle" x="467" y="15" font-weight="600">Gesamt: 1,9 MB analysierter Code</text>
</g>
<g clip-path="url(#mainLangClip)">
<rect x="34.0" y="50" width="377.7" height="14" fill="#a855f7">
<title>C#: 56,2% (1,1 MB)</title>
</rect>
<rect x="411.7" y="50" width="271.5" height="14" fill="#e34c26">
<title>HTML: 40,4% (797,4 KB)</title>
</rect>
<rect x="683.2" y="50" width="15.5" height="14" fill="#563d7c">
<title>CSS: 2,3% (45,7 KB)</title>
</rect>
<rect x="698.6" y="50" width="2.7" height="14" fill="#f1e05a">
<title>JavaScript: 0,4% (8,6 KB)</title>
</rect>
<rect x="701.3" y="50" width="2.7" height="14" fill="#012456">
<title>PowerShell: 0,4% (7,6 KB)</title>
</rect>
<rect x="704.0" y="50" width="2.5" height="14" fill="#89e051">
<title>Shell: 0,2% (3,4 KB)</title>
</rect>
<rect x="706.5" y="50" width="2.5" height="14" fill="#384d54">
<title>Dockerfile: 0,1% (2,4 KB)</title>
</rect>
</g>
<!-- Language Legend Matrix -->
<g transform="translate(34, 84)">
<g transform="translate(0, 0)">
<rect x="0" y="0" width="212" height="22" rx="6" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<circle cx="12" cy="11" r="4.5" fill="#a855f7" />
<text class="lang-name" x="24" y="15">C#</text>
<text class="lang-pct" x="110" y="15">56,2%</text>
<text class="lang-size" x="155" y="15">(1,1 MB)</text>
</g>
<g transform="translate(224, 0)">
<rect x="0" y="0" width="212" height="22" rx="6" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<circle cx="12" cy="11" r="4.5" fill="#e34c26" />
<text class="lang-name" x="24" y="15">HTML</text>
<text class="lang-pct" x="110" y="15">40,4%</text>
<text class="lang-size" x="155" y="15">(797,4 KB)</text>
</g>
<g transform="translate(448, 0)">
<rect x="0" y="0" width="212" height="22" rx="6" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<circle cx="12" cy="11" r="4.5" fill="#563d7c" />
<text class="lang-name" x="24" y="15">CSS</text>
<text class="lang-pct" x="110" y="15">2,3%</text>
<text class="lang-size" x="155" y="15">(45,7 KB)</text>
</g>
<g transform="translate(0, 26)">
<rect x="0" y="0" width="212" height="22" rx="6" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<circle cx="12" cy="11" r="4.5" fill="#f1e05a" />
<text class="lang-name" x="24" y="15">JavaScript</text>
<text class="lang-pct" x="110" y="15">0,4%</text>
<text class="lang-size" x="155" y="15">(8,6 KB)</text>
</g>
<g transform="translate(224, 26)">
<rect x="0" y="0" width="212" height="22" rx="6" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<circle cx="12" cy="11" r="4.5" fill="#012456" />
<text class="lang-name" x="24" y="15">PowerShell</text>
<text class="lang-pct" x="110" y="15">0,4%</text>
<text class="lang-size" x="155" y="15">(7,6 KB)</text>
</g>
<g transform="translate(448, 26)">
<rect x="0" y="0" width="212" height="22" rx="6" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<circle cx="12" cy="11" r="4.5" fill="#89e051" />
<text class="lang-name" x="24" y="15">Shell</text>
<text class="lang-pct" x="110" y="15">0,2%</text>
<text class="lang-size" x="155" y="15">(3,4 KB)</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -69,15 +69,15 @@
<text class="bar-lbl" x="0" y="12">🌆 Abend</text> <text class="bar-lbl" x="0" y="12">🌆 Abend</text>
<text class="bar-time" x="110" y="12">(18:00 - 24:00)</text> <text class="bar-time" x="110" y="12">(18:00 - 24:00)</text>
<rect x="200" y="0" width="430" height="16" rx="4" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" /> <rect x="200" y="0" width="430" height="16" rx="4" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<rect x="200" y="0" width="316.9" height="16" rx="4" fill="var(--bar-evening)" /> <rect x="200" y="0" width="339.3" height="16" rx="4" fill="var(--bar-evening)" />
<text class="stat-val" x="644" y="13" font-size="12">73,7%</text> <text class="stat-val" x="644" y="13" font-size="12">78,9%</text>
</g> </g>
<!-- Bucket: Nacht --> <!-- Bucket: Nacht -->
<g transform="translate(34, 168)"> <g transform="translate(34, 168)">
<text class="bar-lbl" x="0" y="12">🌙 Nacht</text> <text class="bar-lbl" x="0" y="12">🌙 Nacht</text>
<text class="bar-time" x="110" y="12">(00:00 - 06:00)</text> <text class="bar-time" x="110" y="12">(00:00 - 06:00)</text>
<rect x="200" y="0" width="430" height="16" rx="4" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" /> <rect x="200" y="0" width="430" height="16" rx="4" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="0.8" />
<rect x="200" y="0" width="90.7" height="16" rx="4" fill="var(--bar-night)" /> <rect x="200" y="0" width="67.9" height="16" rx="4" fill="var(--bar-night)" />
<text class="stat-val" x="644" y="13" font-size="12">21,1%</text> <text class="stat-val" x="644" y="13" font-size="12">15,8%</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -23,12 +23,14 @@ internal static class Program
var svgPath = GetConfigValue(args, "--output-svg", "OUTPUT_SVG", "heatmap.svg"); var svgPath = GetConfigValue(args, "--output-svg", "OUTPUT_SVG", "heatmap.svg");
var teamSvgPath = GetConfigValue(args, "--team-svg", "TEAM_SVG", "team-stats.svg"); var teamSvgPath = GetConfigValue(args, "--team-svg", "TEAM_SVG", "team-stats.svg");
var prodSvgPath = GetConfigValue(args, "--prod-svg", "PROD_SVG", "productivity.svg"); var prodSvgPath = GetConfigValue(args, "--prod-svg", "PROD_SVG", "productivity.svg");
var langSvgPath = GetConfigValue(args, "--lang-svg", "LANG_SVG", "languages.svg");
var readmePath = GetConfigValue(args, "--readme", "README_PATH", "README.md"); var readmePath = GetConfigValue(args, "--readme", "README_PATH", "README.md");
var repoRoot = FindRepoRoot(); var repoRoot = FindRepoRoot();
if (!Path.IsPathRooted(svgPath)) svgPath = Path.GetFullPath(Path.Combine(repoRoot, svgPath)); if (!Path.IsPathRooted(svgPath)) svgPath = Path.GetFullPath(Path.Combine(repoRoot, svgPath));
if (!Path.IsPathRooted(teamSvgPath)) teamSvgPath = Path.GetFullPath(Path.Combine(repoRoot, teamSvgPath)); if (!Path.IsPathRooted(teamSvgPath)) teamSvgPath = Path.GetFullPath(Path.Combine(repoRoot, teamSvgPath));
if (!Path.IsPathRooted(prodSvgPath)) prodSvgPath = Path.GetFullPath(Path.Combine(repoRoot, prodSvgPath)); if (!Path.IsPathRooted(prodSvgPath)) prodSvgPath = Path.GetFullPath(Path.Combine(repoRoot, prodSvgPath));
if (!Path.IsPathRooted(langSvgPath)) langSvgPath = Path.GetFullPath(Path.Combine(repoRoot, langSvgPath));
if (!Path.IsPathRooted(readmePath)) readmePath = Path.GetFullPath(Path.Combine(repoRoot, readmePath)); if (!Path.IsPathRooted(readmePath)) readmePath = Path.GetFullPath(Path.Combine(repoRoot, readmePath));
Console.WriteLine($"[Config] Target User : {username}"); Console.WriteLine($"[Config] Target User : {username}");
@ -36,6 +38,7 @@ internal static class Program
Console.WriteLine($"[Config] Heatmap SVG : {svgPath}"); Console.WriteLine($"[Config] Heatmap SVG : {svgPath}");
Console.WriteLine($"[Config] Team Stats SVG : {teamSvgPath}"); Console.WriteLine($"[Config] Team Stats SVG : {teamSvgPath}");
Console.WriteLine($"[Config] Productivity SVG : {prodSvgPath}"); Console.WriteLine($"[Config] Productivity SVG : {prodSvgPath}");
Console.WriteLine($"[Config] Languages SVG : {langSvgPath}");
Console.WriteLine($"[Config] Target README : {readmePath}"); Console.WriteLine($"[Config] Target README : {readmePath}");
Console.WriteLine(); Console.WriteLine();
@ -105,7 +108,15 @@ internal static class Program
EnsureDirectory(prodSvgPath); EnsureDirectory(prodSvgPath);
await File.WriteAllTextAsync(prodSvgPath, prodSvg, utf8WithoutBom); await File.WriteAllTextAsync(prodSvgPath, prodSvg, utf8WithoutBom);
// Step 7: Inject all sections into README.md // Step 7: Generate Languages SVG
Console.WriteLine($"[SVG] Rendering Languages SVG: {langSvgPath}");
var teamLangs = stats.TeamStats?.Languages ?? stats.OverallLanguages;
long totalCodeBytes = stats.TeamStats?.TotalCodeBytes ?? teamLangs.Sum(l => l.Bytes);
var langSvg = SvgGenerator.GenerateLanguages(teamLangs, totalCodeBytes);
EnsureDirectory(langSvgPath);
await File.WriteAllTextAsync(langSvgPath, langSvg, utf8WithoutBom);
// Step 8: Inject all sections into README.md
if (File.Exists(readmePath)) if (File.Exists(readmePath))
{ {
Console.WriteLine($"[README] Injecting personal, team & language metrics into: {readmePath}"); Console.WriteLine($"[README] Injecting personal, team & language metrics into: {readmePath}");

View File

@ -96,14 +96,19 @@ public static class ReadmeInjector
content = ReplaceSection(content, StartLangMarker, EndLangMarker, sb => content = ReplaceSection(content, StartLangMarker, EndLangMarker, sb =>
{ {
var langs = stats.TeamStats?.Languages ?? stats.OverallLanguages; var langs = stats.TeamStats?.Languages ?? stats.OverallLanguages;
sb.AppendLine("| Sprache | Anteil | Code-Volumen | Verteilung |"); sb.AppendLine("| Sprache | Technologie | Anteil | Code-Volumen | Relative Verteilung |");
sb.AppendLine("| :--- | :---: | :---: | :--- |"); sb.AppendLine("| :--- | :---: | :---: | :---: | :--- |");
foreach (var l in langs.Take(6)) foreach (var l in langs.Take(6))
{ {
int filledBlocks = (int)Math.Round(l.Percentage / 5.0); var (logo, logoColor) = GetLanguageBadgeInfo(l.Name);
string bar = new string('█', filledBlocks).PadRight(20, '░'); var colorHex = l.Color.TrimStart('#');
sb.AppendLine($"| `{l.Name}` | **{l.Percentage:F1}%** | {l.FormattedSize} | `{bar}` |"); var badgeUrl = $"https://img.shields.io/badge/{Uri.EscapeDataString(l.Name)}-{colorHex}?style=flat-square&logo={logo}&logoColor={logoColor}";
int filledBlocks = Math.Max(1, (int)Math.Round(l.Percentage / 5.0));
string bar = new string('▰', filledBlocks).PadRight(20, '▱');
sb.AppendLine($"| **{l.Name}** | ![{l.Name}]({badgeUrl}) | **{l.Percentage:F1}%** | `{l.FormattedSize}` | `{bar}` |");
} }
}); });
@ -210,4 +215,18 @@ public static class ReadmeInjector
if (diff.TotalDays < 30) return $"vor {(int)diff.TotalDays} Tagen"; if (diff.TotalDays < 30) return $"vor {(int)diff.TotalDays} Tagen";
return dateTime.ToString("dd.MM.yyyy"); return dateTime.ToString("dd.MM.yyyy");
} }
private static (string Logo, string LogoColor) GetLanguageBadgeInfo(string lang) => lang.ToLowerInvariant() switch
{
"c#" => ("csharp", "white"),
"html" => ("html5", "white"),
"css" => ("css3", "white"),
"javascript" => ("javascript", "black"),
"typescript" => ("typescript", "white"),
"powershell" => ("powershell", "white"),
"shell" or "bash" => ("gnubash", "white"),
"dockerfile" => ("docker", "white"),
"python" => ("python", "white"),
_ => ("code", "white")
};
} }

View File

@ -429,6 +429,121 @@ public static class SvgGenerator
return sb.ToString(); return sb.ToString();
} }
public static string GenerateLanguages(List<LanguageStat> languages, long totalBytes)
{
var sb = new StringBuilder(8192);
const int svgWidth = 740;
const int svgHeight = 150;
const int barStartX = 34;
const double barWidth = 672.0;
string formattedTotal = totalBytes switch
{
>= 1024 * 1024 => $"{(double)totalBytes / (1024 * 1024):F1} MB",
>= 1024 => $"{(double)totalBytes / 1024:F1} KB",
_ => $"{totalBytes} B"
};
sb.AppendLine("""<?xml version="1.0" encoding="utf-8"?>""");
sb.AppendLine($"""<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {svgWidth} {svgHeight}" width="100%" height="100%">""");
sb.AppendLine(" <defs>");
sb.AppendLine(" <clipPath id=\"mainLangClip\">");
sb.AppendLine($" <rect x=\"{barStartX}\" y=\"50\" width=\"{barWidth}\" height=\"14\" rx=\"7\" />");
sb.AppendLine(" </clipPath>");
sb.AppendLine(" </defs>");
sb.AppendLine("""
<style>
:root {
--bg: #11111b;
--card: #181825;
--border: #313244;
--text-main: #cdd6f4;
--text-sub: #a6adc8;
--text-muted: #6c7086;
--accent: #d980fa;
--metric-bg: #1e1e2e;
--metric-border: #313244;
}
@media (prefers-color-scheme: light) {
:root {
--bg: #f8fafc;
--card: #ffffff;
--border: #e2e8f0;
--text-main: #0f172a;
--text-sub: #334155;
--text-muted: #64748b;
--accent: #7e22ce;
--metric-bg: #f1f5f9;
--metric-border: #cbd5e1;
}
}
.bg-panel { fill: var(--bg); stroke: var(--border); stroke-width: 1.2px; rx: 12px; }
.title { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: 700; fill: var(--text-main); }
.subtitle { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; fill: var(--text-sub); }
.lang-name { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 600; fill: var(--text-main); }
.lang-pct { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 700; fill: var(--accent); }
.lang-size { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 10px; fill: var(--text-muted); }
</style>
""");
// Background
sb.AppendLine($" <rect class=\"bg-panel\" x=\"1\" y=\"1\" width=\"{svgWidth - 2}\" height=\"{svgHeight - 2}\" />");
// Header
sb.AppendLine(" <g transform=\"translate(24, 18)\">");
sb.AppendLine(" <text class=\"title\" x=\"0\" y=\"14\">📈 Byte-genaue Sprachverteilung <tspan font-weight=\"400\" fill=\"var(--accent)\">•</tspan> <tspan font-weight=\"500\" font-size=\"11\" fill=\"var(--text-sub)\">Team-Codebase</tspan></text>");
string badgeText = $"Gesamt: {formattedTotal} analysierter Code";
sb.AppendLine($" <rect x=\"445\" y=\"0\" width=\"255\" height=\"22\" rx=\"11\" fill=\"var(--metric-bg)\" stroke=\"var(--metric-border)\" stroke-width=\"1\" />");
sb.AppendLine($" <circle cx=\"457\" cy=\"11\" r=\"4\" fill=\"var(--accent)\" />");
sb.AppendLine($" <text class=\"subtitle\" x=\"467\" y=\"15\" font-weight=\"600\">{badgeText}</text>");
sb.AppendLine(" </g>");
// Segmented Progress Bar
sb.AppendLine(" <g clip-path=\"url(#mainLangClip)\">");
double currentX = barStartX;
foreach (var lang in languages)
{
double w = Math.Max(2.5, (lang.Percentage / 100.0) * barWidth);
sb.AppendLine($" <rect x=\"{currentX.ToString("F1", CultureInfo.InvariantCulture)}\" y=\"50\" width=\"{w.ToString("F1", CultureInfo.InvariantCulture)}\" height=\"14\" fill=\"{lang.Color}\">");
sb.AppendLine($" <title>{XmlEscape(lang.Name)}: {lang.Percentage:F1}% ({lang.FormattedSize})</title>");
sb.AppendLine(" </rect>");
currentX += w;
}
sb.AppendLine(" </g>");
// 3 Columns x 2 Rows Grid
sb.AppendLine(" <!-- Language Legend Matrix -->");
sb.AppendLine(" <g transform=\"translate(34, 84)\">");
int colWidth = 224;
int rowHeight = 26;
for (int i = 0; i < languages.Count && i < 6; i++)
{
var l = languages[i];
int col = i % 3;
int row = i / 3;
int x = col * colWidth;
int y = row * rowHeight;
sb.AppendLine($" <g transform=\"translate({x}, {y})\">");
sb.AppendLine($" <rect x=\"0\" y=\"0\" width=\"212\" height=\"22\" rx=\"6\" fill=\"var(--metric-bg)\" stroke=\"var(--metric-border)\" stroke-width=\"0.8\" />");
sb.AppendLine($" <circle cx=\"12\" cy=\"11\" r=\"4.5\" fill=\"{l.Color}\" />");
sb.AppendLine($" <text class=\"lang-name\" x=\"24\" y=\"15\">{XmlEscape(l.Name)}</text>");
sb.AppendLine($" <text class=\"lang-pct\" x=\"110\" y=\"15\">{l.Percentage:F1}%</text>");
sb.AppendLine($" <text class=\"lang-size\" x=\"155\" y=\"15\">({l.FormattedSize})</text>");
sb.AppendLine(" </g>");
}
sb.AppendLine(" </g>");
sb.AppendLine("</svg>");
return sb.ToString();
}
private static string XmlEscape(string? text) private static string XmlEscape(string? text)
{ {
if (string.IsNullOrEmpty(text)) return string.Empty; if (string.IsNullOrEmpty(text)) return string.Empty;