feat: add productivity biorhythm clock, developer achievements and quote [skip ci]
This commit is contained in:
parent
3ba3863f54
commit
c4c47ab445
@ -13,6 +13,7 @@ on:
|
|||||||
paths-ignore:
|
paths-ignore:
|
||||||
- 'heatmap.svg'
|
- 'heatmap.svg'
|
||||||
- 'team-stats.svg'
|
- 'team-stats.svg'
|
||||||
|
- 'productivity.svg'
|
||||||
- 'README.md'
|
- 'README.md'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -50,9 +51,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|README.md)"; then
|
if git status --porcelain | grep -E "(heatmap.svg|team-stats.svg|productivity.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 README.md
|
git add heatmap.svg team-stats.svg productivity.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]"
|
||||||
|
|||||||
63
README.md
63
README.md
@ -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%3A12_UTC-7e3f98?style=flat-square" alt="Last Sync" />
|
<img src="https://img.shields.io/badge/Telemetry-Zuletzt_aktualisiert_02.09.2026%2019%3A15_UTC-7e3f98?style=flat-square" alt="Last Sync" />
|
||||||
</p>
|
</p>
|
||||||
<!-- END_SYS_TELEMETRY -->
|
<!-- END_SYS_TELEMETRY -->
|
||||||
|
|
||||||
@ -115,11 +115,11 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
|
|||||||
> *Echtzeit-Feed der letzten Aktionen und Commits über alle privaten & Team-Repositories.*
|
> *Echtzeit-Feed der letzten Aktionen und Commits über alle privaten & Team-Repositories.*
|
||||||
|
|
||||||
<!-- START_RECENT_ACTIVITY -->
|
<!-- START_RECENT_ACTIVITY -->
|
||||||
- ⚡ **feat: add team telemetry dashboard, byte-level language analytics and tabs [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 2 Minuten*
|
- ⚡ **feat: add live commit radar, activity feed and homelab telemetry [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 2 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 10 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 6 Minuten*
|
||||||
- ⚡ **chore(profile): update activity heatmap & stats (22 contributions) [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 18 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 14 Minuten*
|
||||||
- ⚡ **Initial profile setup** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 19 Minuten*
|
- ⚡ **chore(profile): update activity heatmap & stats (22 contributions) [skip ci]** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 21 Minuten*
|
||||||
- ✨ **Repository neu erstellt** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 55 Minuten*
|
- ⚡ **Initial profile setup** auf [`NickiCloud/.profile`](https://git.webcore-consulting.dev/NickiCloud/.profile) • 🕒 *vor 22 Minuten*
|
||||||
<!-- END_RECENT_ACTIVITY -->
|
<!-- END_RECENT_ACTIVITY -->
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@ -127,7 +127,37 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
|
|||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<details open>
|
<details open>
|
||||||
<summary><h3>🛡️ Tab 4: Homelab, Server-Infrastruktur & Telemetrie</h3></summary>
|
<summary><h3>⏱️ Tab 4: Coding Biorhythmus, Produktivität & Trophäen</h3></summary>
|
||||||
|
|
||||||
|
> *Analyse der aktiven Entwicklungsphasen, Trophäen und Entwickler-Weisheiten.*
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<img src="productivity.svg" alt="Nicki's Coding Productivity Clock" width="100%" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
#### 🏆 Entwickler-Erfolge & 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 | **26,3% Nachts** ✅ |
|
||||||
|
| 🏰 **Homelab Overlord** | `Legendary` | Self-Hosted Debian, Docker, Gitea, Mailcow | **Vollautonom** ✅ |
|
||||||
|
<!-- END_ACHIEVEMENTS -->
|
||||||
|
|
||||||
|
#### 💡 Tägliche Entwickler-Philosophie:
|
||||||
|
<!-- START_RANDOM_QUOTE -->
|
||||||
|
> *"Clean code always looks like it was written by someone who cares."*
|
||||||
|
> — **Robert C. Martin**
|
||||||
|
<!-- END_RANDOM_QUOTE -->
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary><h3>🛡️ Tab 5: Homelab, Server-Infrastruktur & Telemetrie</h3></summary>
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
@ -158,6 +188,19 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<details open>
|
||||||
|
<summary><h3>🎮 Tab 6: Gaming Battlestation & Loadouts</h3></summary>
|
||||||
|
|
||||||
|
| Game | Fokus & Rolle | Lieblingselement / Operator |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| ⛏️ **Minecraft** | Technical Automation & Redstone | Automatisierte Farmen, Create Mod, Server-Hosting |
|
||||||
|
| 🎯 **Rainbow Six Siege** | Tactical Anchor & Area Denial | Smoke, Mute, Thermite, Thatcher |
|
||||||
|
| ⚔️ **Warframe** | High-Level Survival & Grinding | Mesa Prime, Wisp, Volt |
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Ausgewählte Eigenprojekte
|
## 🛠️ Ausgewählte Eigenprojekte
|
||||||
@ -173,12 +216,6 @@ Ich bin **Nicki (Nicklas)**, angehender Softwareentwickler mit fundiertem Fokus
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
### 🎮 Gaming & Fun Corner
|
|
||||||
|
|
||||||
[](https://minecraft.net)
|
|
||||||
[](https://www.ubisoft.com/game/rainbow-six/siege)
|
|
||||||
[](https://www.warframe.com/)
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var nicki = new Developer
|
var nicki = new Developer
|
||||||
{
|
{
|
||||||
|
|||||||
30
heatmap.svg
30
heatmap.svg
@ -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">25 Commits im letzten Jahr</text>
|
<text class="subtitle" x="532" y="15" font-weight="600">26 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>
|
||||||
@ -1172,8 +1172,8 @@
|
|||||||
<rect class="cell lvl-3" x="697" y="77" width="10" height="10" rx="2" style="animation-delay: 612ms;">
|
<rect class="cell lvl-3" x="697" y="77" width="10" height="10" rx="2" style="animation-delay: 612ms;">
|
||||||
<title>01.09.2026: 9 Commits</title>
|
<title>01.09.2026: 9 Commits</title>
|
||||||
</rect>
|
</rect>
|
||||||
<rect class="cell lvl-3" 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: 9 Commits</title>
|
<title>02.09.2026: 10 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">25</text>
|
<text class="stat-val" x="40" y="20">26</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)">
|
||||||
@ -1209,30 +1209,30 @@
|
|||||||
</g>
|
</g>
|
||||||
<!-- Top Languages Bar -->
|
<!-- Top Languages Bar -->
|
||||||
<g clip-path="url(#langBarClip)">
|
<g clip-path="url(#langBarClip)">
|
||||||
<rect x="34.0" y="215" width="387.3" height="8" fill="#a855f7">
|
<rect x="34.0" y="215" width="388.0" height="8" fill="#a855f7">
|
||||||
<title>C#: 57,3%</title>
|
<title>C#: 57,4%</title>
|
||||||
</rect>
|
</rect>
|
||||||
<rect x="421.3" y="215" width="266.3" height="8" fill="#e34c26">
|
<rect x="422.0" y="215" width="265.0" height="8" fill="#e34c26">
|
||||||
<title>HTML: 39,4%</title>
|
<title>HTML: 39,2%</title>
|
||||||
</rect>
|
</rect>
|
||||||
<rect x="687.7" y="215" width="15.5" height="8" fill="#563d7c">
|
<rect x="687.0" y="215" width="14.9" height="8" fill="#563d7c">
|
||||||
<title>CSS: 2,3%</title>
|
<title>CSS: 2,2%</title>
|
||||||
</rect>
|
</rect>
|
||||||
<rect x="703.2" y="215" width="2.7" height="8" fill="#f1e05a">
|
<rect x="701.9" y="215" width="2.7" height="8" fill="#f1e05a">
|
||||||
<title>JavaScript: 0,4%</title>
|
<title>JavaScript: 0,4%</title>
|
||||||
</rect>
|
</rect>
|
||||||
<rect x="705.9" y="215" width="2.7" height="8" fill="#012456">
|
<rect x="704.6" y="215" width="2.7" height="8" fill="#012456">
|
||||||
<title>PowerShell: 0,4%</title>
|
<title>PowerShell: 0,4%</title>
|
||||||
</rect>
|
</rect>
|
||||||
</g>
|
</g>
|
||||||
<!-- Language Legend -->
|
<!-- Language Legend -->
|
||||||
<g transform="translate(34, 238)">
|
<g transform="translate(34, 238)">
|
||||||
<circle cx="4" cy="4" r="4" fill="#a855f7" />
|
<circle cx="4" cy="4" r="4" fill="#a855f7" />
|
||||||
<text class="lang-lbl" x="13" y="7">C# 57,3%</text>
|
<text class="lang-lbl" x="13" y="7">C# 57,4%</text>
|
||||||
<circle cx="89" cy="4" r="4" fill="#e34c26" />
|
<circle cx="89" cy="4" r="4" fill="#e34c26" />
|
||||||
<text class="lang-lbl" x="98" y="7">HTML 39,4%</text>
|
<text class="lang-lbl" x="98" y="7">HTML 39,2%</text>
|
||||||
<circle cx="188" cy="4" r="4" fill="#563d7c" />
|
<circle cx="188" cy="4" r="4" fill="#563d7c" />
|
||||||
<text class="lang-lbl" x="197" y="7">CSS 2,3%</text>
|
<text class="lang-lbl" x="197" y="7">CSS 2,2%</text>
|
||||||
<circle cx="273" cy="4" r="4" fill="#f1e05a" />
|
<circle cx="273" cy="4" r="4" fill="#f1e05a" />
|
||||||
<text class="lang-lbl" x="282" y="7">JavaScript 0,4%</text>
|
<text class="lang-lbl" x="282" y="7">JavaScript 0,4%</text>
|
||||||
<circle cx="407" cy="4" r="4" fill="#012456" />
|
<circle cx="407" cy="4" r="4" fill="#012456" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
83
productivity.svg
Normal file
83
productivity.svg
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 740 220" width="100%" height="100%">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #11111b;
|
||||||
|
--card: #181825;
|
||||||
|
--border: #313244;
|
||||||
|
--text-main: #cdd6f4;
|
||||||
|
--text-sub: #a6adc8;
|
||||||
|
--text-muted: #6c7086;
|
||||||
|
--accent: #d980fa;
|
||||||
|
--bar-morning: #f59e0b;
|
||||||
|
--bar-afternoon: #3b82f6;
|
||||||
|
--bar-evening: #a855f7;
|
||||||
|
--bar-night: #6366f1;
|
||||||
|
--metric-bg: #1e1e2e;
|
||||||
|
--metric-border: #313244;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--bg: #fdf4ff;
|
||||||
|
--card: #ffffff;
|
||||||
|
--border: #f0abfc;
|
||||||
|
--text-main: #4a044e;
|
||||||
|
--text-sub: #701a75;
|
||||||
|
--text-muted: #a21caf;
|
||||||
|
--accent: #c026d3;
|
||||||
|
--bar-morning: #d97706;
|
||||||
|
--bar-afternoon: #2563eb;
|
||||||
|
--bar-evening: #9333ea;
|
||||||
|
--bar-night: #4f46e5;
|
||||||
|
--metric-bg: #fae8ff;
|
||||||
|
--metric-border: #f5d0fe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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: 14px; 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); }
|
||||||
|
.stat-val { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: 700; fill: var(--text-main); }
|
||||||
|
.stat-lbl { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 10px; fill: var(--text-sub); }
|
||||||
|
.bar-lbl { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 600; fill: var(--text-main); }
|
||||||
|
.bar-time { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 9px; fill: var(--text-muted); }
|
||||||
|
</style>
|
||||||
|
<rect class="bg-panel" x="1" y="1" width="738" height="218" />
|
||||||
|
<g transform="translate(24, 20)">
|
||||||
|
<text class="title" x="0" y="14">⏱️ Coding Biorhythmus <tspan font-weight="400" fill="var(--accent)">•</tspan> <tspan font-weight="500" font-size="12" fill="var(--text-sub)">Aktivitäts-Phasen (CET)</tspan></text>
|
||||||
|
<rect x="390" y="0" width="310" height="22" rx="11" fill="var(--metric-bg)" stroke="var(--metric-border)" stroke-width="1" />
|
||||||
|
<circle cx="402" cy="11" r="4" fill="var(--accent)" />
|
||||||
|
<text class="subtitle" x="412" y="15" font-weight="600">🌙 Night-Owl (Höchste Produktivität am Abend & nachts)</text>
|
||||||
|
</g>
|
||||||
|
<!-- Bucket: Morgen -->
|
||||||
|
<g transform="translate(34, 60)">
|
||||||
|
<text class="bar-lbl" x="0" y="12">🌅 Morgen</text>
|
||||||
|
<text class="bar-time" x="110" y="12">(06:00 - 12: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="4.0" height="16" rx="4" fill="var(--bar-morning)" />
|
||||||
|
<text class="stat-val" x="644" y="13" font-size="12">0,0%</text>
|
||||||
|
</g>
|
||||||
|
<!-- Bucket: Nachmittag -->
|
||||||
|
<g transform="translate(34, 96)">
|
||||||
|
<text class="bar-lbl" x="0" y="12">☀️ Nachmittag</text>
|
||||||
|
<text class="bar-time" x="110" y="12">(12:00 - 18: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="22.8" height="16" rx="4" fill="var(--bar-afternoon)" />
|
||||||
|
<text class="stat-val" x="644" y="13" font-size="12">5,3%</text>
|
||||||
|
</g>
|
||||||
|
<!-- Bucket: Abend -->
|
||||||
|
<g transform="translate(34, 132)">
|
||||||
|
<text class="bar-lbl" x="0" y="12">🌆 Abend</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="294.1" height="16" rx="4" fill="var(--bar-evening)" />
|
||||||
|
<text class="stat-val" x="644" y="13" font-size="12">68,4%</text>
|
||||||
|
</g>
|
||||||
|
<!-- Bucket: Nacht -->
|
||||||
|
<g transform="translate(34, 168)">
|
||||||
|
<text class="bar-lbl" x="0" y="12">🌙 Nacht</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="113.1" height="16" rx="4" fill="var(--bar-night)" />
|
||||||
|
<text class="stat-val" x="644" y="13" font-size="12">26,3%</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.5 KiB |
@ -26,6 +26,20 @@ public sealed class DayContribution
|
|||||||
public required int Row { get; init; } // 0..6 (0=Mo..6=So)
|
public required int Row { get; init; } // 0..6 (0=Mo..6=So)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ProductivityStats
|
||||||
|
{
|
||||||
|
public required double MorningPct { get; init; }
|
||||||
|
public required double AfternoonPct { get; init; }
|
||||||
|
public required double EveningPct { get; init; }
|
||||||
|
public required double NightPct { get; init; }
|
||||||
|
public required int MorningCount { get; init; }
|
||||||
|
public required int AfternoonCount { get; init; }
|
||||||
|
public required int EveningCount { get; init; }
|
||||||
|
public required int NightCount { get; init; }
|
||||||
|
public required string MostActiveDayOfWeek { get; init; }
|
||||||
|
public required string DevArchetype { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class TeamStats
|
public sealed class TeamStats
|
||||||
{
|
{
|
||||||
public required string OrganizationName { get; init; }
|
public required string OrganizationName { get; init; }
|
||||||
@ -54,4 +68,5 @@ public sealed class ProfileStats
|
|||||||
public required List<GiteaRepo> PersonalRepos { get; init; }
|
public required List<GiteaRepo> PersonalRepos { get; init; }
|
||||||
public required TeamStats? TeamStats { get; init; }
|
public required TeamStats? TeamStats { get; init; }
|
||||||
public required List<ActivityItem> RecentActivities { get; init; }
|
public required List<ActivityItem> RecentActivities { get; init; }
|
||||||
|
public required ProductivityStats Productivity { get; init; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,22 +15,27 @@ internal static class Program
|
|||||||
====================================================
|
====================================================
|
||||||
""");
|
""");
|
||||||
|
|
||||||
|
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||||
|
|
||||||
var username = GetConfigValue(args, "--username", "GITEA_USERNAME", "NickiCloud");
|
var username = GetConfigValue(args, "--username", "GITEA_USERNAME", "NickiCloud");
|
||||||
var apiUrl = GetConfigValue(args, "--api-url", "GITEA_API_URL", "https://git.webcore-consulting.dev/api/v1");
|
var apiUrl = GetConfigValue(args, "--api-url", "GITEA_API_URL", "https://git.webcore-consulting.dev/api/v1");
|
||||||
var token = GetConfigValue(args, "--token", "GITEA_TOKEN", string.Empty);
|
var token = GetConfigValue(args, "--token", "GITEA_TOKEN", string.Empty);
|
||||||
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 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(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}");
|
||||||
Console.WriteLine($"[Config] API Endpoint : {apiUrl}");
|
Console.WriteLine($"[Config] API Endpoint : {apiUrl}");
|
||||||
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] Target README : {readmePath}");
|
Console.WriteLine($"[Config] Target README : {readmePath}");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
|
|
||||||
@ -94,15 +99,22 @@ internal static class Program
|
|||||||
await File.WriteAllTextAsync(teamSvgPath, teamSvg, utf8WithoutBom);
|
await File.WriteAllTextAsync(teamSvgPath, teamSvg, utf8WithoutBom);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Inject all sections into README.md
|
// Step 6: Generate Productivity SVG
|
||||||
|
Console.WriteLine($"[SVG] Rendering Productivity SVG: {prodSvgPath}");
|
||||||
|
var prodSvg = SvgGenerator.GenerateProductivity(stats.Productivity);
|
||||||
|
EnsureDirectory(prodSvgPath);
|
||||||
|
await File.WriteAllTextAsync(prodSvgPath, prodSvg, utf8WithoutBom);
|
||||||
|
|
||||||
|
// Step 7: 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}");
|
||||||
ReadmeInjector.InjectAll(readmePath, stats);
|
ReadmeInjector.InjectAll(readmePath, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
Console.ForegroundColor = ConsoleColor.Green;
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
Console.WriteLine("\n✨ Multi-tier profile update completed successfully!");
|
Console.WriteLine($"\n✨ Multi-tier profile update completed successfully in {sw.ElapsedMilliseconds}ms!");
|
||||||
Console.ResetColor();
|
Console.ResetColor();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,12 @@ public static class ReadmeInjector
|
|||||||
private const string StartTelemetryMarker = "<!-- START_SYS_TELEMETRY -->";
|
private const string StartTelemetryMarker = "<!-- START_SYS_TELEMETRY -->";
|
||||||
private const string EndTelemetryMarker = "<!-- END_SYS_TELEMETRY -->";
|
private const string EndTelemetryMarker = "<!-- END_SYS_TELEMETRY -->";
|
||||||
|
|
||||||
|
private const string StartQuoteMarker = "<!-- START_RANDOM_QUOTE -->";
|
||||||
|
private const string EndQuoteMarker = "<!-- END_RANDOM_QUOTE -->";
|
||||||
|
|
||||||
|
private const string StartAchievementsMarker = "<!-- START_ACHIEVEMENTS -->";
|
||||||
|
private const string EndAchievementsMarker = "<!-- END_ACHIEVEMENTS -->";
|
||||||
|
|
||||||
public static bool InjectAll(string readmePath, ProfileStats stats)
|
public static bool InjectAll(string readmePath, ProfileStats stats)
|
||||||
{
|
{
|
||||||
if (!File.Exists(readmePath))
|
if (!File.Exists(readmePath))
|
||||||
@ -139,9 +145,40 @@ public static class ReadmeInjector
|
|||||||
sb.AppendLine($"</p>");
|
sb.AppendLine($"</p>");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 6. Inject Developer Achievements
|
||||||
|
content = ReplaceSection(content, StartAchievementsMarker, EndAchievementsMarker, sb =>
|
||||||
|
{
|
||||||
|
sb.AppendLine("| Trophy | Rang | Beschreibung | Status |");
|
||||||
|
sb.AppendLine("| :--- | :---: | :--- | :---: |");
|
||||||
|
sb.AppendLine($"| 🏆 **Streak Master** | `Gold` | Mindestens 4 Tage ununterbrochen aktiv gecodet | **{stats.CurrentStreak} Tage** ✅ |");
|
||||||
|
sb.AppendLine($"| 💾 **Code Sovereign** | `Diamond` | Über 1,5 MB Code in Gitea verwaltet | **{stats.TeamStats?.FormattedTotalSize ?? "1,9 MB"}** ✅ |");
|
||||||
|
sb.AppendLine($"| ⚡ **Zero-Dependency** | `Master` | Reines .NET 10 BCL ohne fremde NuGet-Pakete | **100% Native** ✅ |");
|
||||||
|
sb.AppendLine($"| 🌙 **Night-Owl Veteran** | `Elite` | Hohe nächtliche Coding-Aktivität | **{stats.Productivity.NightPct:F1}% Nachts** ✅ |");
|
||||||
|
sb.AppendLine($"| 🏰 **Homelab Overlord** | `Legendary` | Self-Hosted Debian, Docker, Gitea, Mailcow | **Vollautonom** ✅ |");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 7. Inject Rotating Dev Wisdom Quote
|
||||||
|
content = ReplaceSection(content, StartQuoteMarker, EndQuoteMarker, sb =>
|
||||||
|
{
|
||||||
|
var quotes = new[]
|
||||||
|
{
|
||||||
|
("Measure twice, refactor once, ship autonomously.", "Nicki"),
|
||||||
|
("Premature optimization is the root of all evil – but allocating in hot loops is a close second.", "Donald Knuth / Modern .NET"),
|
||||||
|
("Simplicity is prerequisite for reliability.", "Edsger W. Dijkstra"),
|
||||||
|
("First, solve the problem. Then, write the code in C#.", "John Johnson"),
|
||||||
|
("There are only two hard things in Computer Science: cache invalidation and naming things.", "Phil Karlton"),
|
||||||
|
("Clean code always looks like it was written by someone who cares.", "Robert C. Martin")
|
||||||
|
};
|
||||||
|
|
||||||
|
int dayOfYear = DateTime.UtcNow.DayOfYear;
|
||||||
|
var (text, author) = quotes[dayOfYear % quotes.Length];
|
||||||
|
sb.AppendLine($"> *\"{text}\"* ");
|
||||||
|
sb.AppendLine($"> — **{author}**");
|
||||||
|
});
|
||||||
|
|
||||||
var utf8WithoutBom = new UTF8Encoding(false);
|
var utf8WithoutBom = new UTF8Encoding(false);
|
||||||
File.WriteAllText(readmePath, content, utf8WithoutBom);
|
File.WriteAllText(readmePath, content, utf8WithoutBom);
|
||||||
Console.WriteLine($"[README] Successfully updated all 5 dynamic sections in {readmePath}");
|
Console.WriteLine($"[README] Successfully updated all 7 dynamic sections in {readmePath}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -133,7 +133,75 @@ public static class StatsCalculator
|
|||||||
var overallLanguages = AggregateLanguages(allRepos);
|
var overallLanguages = AggregateLanguages(allRepos);
|
||||||
var teamLanguages = AggregateLanguages(teamRepos);
|
var teamLanguages = AggregateLanguages(teamRepos);
|
||||||
|
|
||||||
// 6. Build TeamStats
|
// 6. Productivity & Coding Biorhythm Analysis
|
||||||
|
int morningCount = 0, afternoonCount = 0, eveningCount = 0, nightCount = 0;
|
||||||
|
var dayOfWeekMap = new Dictionary<DayOfWeek, int>();
|
||||||
|
|
||||||
|
foreach (var act in recentActivities)
|
||||||
|
{
|
||||||
|
var localTime = act.Created.AddHours(2); // Europe/Berlin (CEST)
|
||||||
|
int hour = localTime.Hour;
|
||||||
|
if (hour >= 6 && hour < 12) morningCount++;
|
||||||
|
else if (hour >= 12 && hour < 18) afternoonCount++;
|
||||||
|
else if (hour >= 18 && hour < 24) eveningCount++;
|
||||||
|
else nightCount++;
|
||||||
|
|
||||||
|
dayOfWeekMap[localTime.DayOfWeek] = dayOfWeekMap.GetValueOrDefault(localTime.DayOfWeek, 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incorporate repo update timestamps for richer sample size
|
||||||
|
foreach (var repo in allRepos)
|
||||||
|
{
|
||||||
|
var localTime = repo.UpdatedAt.AddHours(2);
|
||||||
|
int hour = localTime.Hour;
|
||||||
|
if (hour >= 6 && hour < 12) morningCount++;
|
||||||
|
else if (hour >= 12 && hour < 18) afternoonCount++;
|
||||||
|
else if (hour >= 18 && hour < 24) eveningCount++;
|
||||||
|
else nightCount++;
|
||||||
|
|
||||||
|
dayOfWeekMap[localTime.DayOfWeek] = dayOfWeekMap.GetValueOrDefault(localTime.DayOfWeek, 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalEvents = morningCount + afternoonCount + eveningCount + nightCount;
|
||||||
|
if (totalEvents == 0) totalEvents = 1;
|
||||||
|
|
||||||
|
double morningPct = Math.Round((double)morningCount / totalEvents * 100.0, 1);
|
||||||
|
double afternoonPct = Math.Round((double)afternoonCount / totalEvents * 100.0, 1);
|
||||||
|
double eveningPct = Math.Round((double)eveningCount / totalEvents * 100.0, 1);
|
||||||
|
double nightPct = Math.Round((double)nightCount / totalEvents * 100.0, 1);
|
||||||
|
|
||||||
|
var topDay = dayOfWeekMap.Count > 0 ? dayOfWeekMap.OrderByDescending(kv => kv.Value).First().Key : DayOfWeek.Wednesday;
|
||||||
|
string topDayName = topDay switch
|
||||||
|
{
|
||||||
|
DayOfWeek.Monday => "Montag",
|
||||||
|
DayOfWeek.Tuesday => "Dienstag",
|
||||||
|
DayOfWeek.Wednesday => "Mittwoch",
|
||||||
|
DayOfWeek.Thursday => "Donnerstag",
|
||||||
|
DayOfWeek.Friday => "Freitag",
|
||||||
|
DayOfWeek.Saturday => "Samstag",
|
||||||
|
DayOfWeek.Sunday => "Sonntag",
|
||||||
|
_ => "Mittwoch"
|
||||||
|
};
|
||||||
|
|
||||||
|
string archetype = (nightCount + eveningCount >= morningCount + afternoonCount)
|
||||||
|
? "🌙 Night-Owl (Höchste Produktivität am Abend & nachts)"
|
||||||
|
: "⚡ Day-Runner (Fokus während der regulären Tagesstunden)";
|
||||||
|
|
||||||
|
var productivity = new ProductivityStats
|
||||||
|
{
|
||||||
|
MorningPct = morningPct,
|
||||||
|
AfternoonPct = afternoonPct,
|
||||||
|
EveningPct = eveningPct,
|
||||||
|
NightPct = nightPct,
|
||||||
|
MorningCount = morningCount,
|
||||||
|
AfternoonCount = afternoonCount,
|
||||||
|
EveningCount = eveningCount,
|
||||||
|
NightCount = nightCount,
|
||||||
|
MostActiveDayOfWeek = topDayName,
|
||||||
|
DevArchetype = archetype
|
||||||
|
};
|
||||||
|
|
||||||
|
// 7. Build TeamStats
|
||||||
TeamStats? teamStats = null;
|
TeamStats? teamStats = null;
|
||||||
if (teamRepos.Count > 0 || orgs.Count > 0)
|
if (teamRepos.Count > 0 || orgs.Count > 0)
|
||||||
{
|
{
|
||||||
@ -164,7 +232,8 @@ public static class StatsCalculator
|
|||||||
OverallLanguages = overallLanguages,
|
OverallLanguages = overallLanguages,
|
||||||
PersonalRepos = personalRepos,
|
PersonalRepos = personalRepos,
|
||||||
TeamStats = teamStats,
|
TeamStats = teamStats,
|
||||||
RecentActivities = recentActivities
|
RecentActivities = recentActivities,
|
||||||
|
Productivity = productivity
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -325,6 +325,110 @@ public static class SvgGenerator
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GenerateProductivity(ProductivityStats prod)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder(8192);
|
||||||
|
|
||||||
|
const int svgWidth = 740;
|
||||||
|
const int svgHeight = 220;
|
||||||
|
const int gridStartX = 34;
|
||||||
|
|
||||||
|
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("""
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #11111b;
|
||||||
|
--card: #181825;
|
||||||
|
--border: #313244;
|
||||||
|
--text-main: #cdd6f4;
|
||||||
|
--text-sub: #a6adc8;
|
||||||
|
--text-muted: #6c7086;
|
||||||
|
--accent: #d980fa;
|
||||||
|
--bar-morning: #f59e0b;
|
||||||
|
--bar-afternoon: #3b82f6;
|
||||||
|
--bar-evening: #a855f7;
|
||||||
|
--bar-night: #6366f1;
|
||||||
|
--metric-bg: #1e1e2e;
|
||||||
|
--metric-border: #313244;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
--bg: #fdf4ff;
|
||||||
|
--card: #ffffff;
|
||||||
|
--border: #f0abfc;
|
||||||
|
--text-main: #4a044e;
|
||||||
|
--text-sub: #701a75;
|
||||||
|
--text-muted: #a21caf;
|
||||||
|
--accent: #c026d3;
|
||||||
|
--bar-morning: #d97706;
|
||||||
|
--bar-afternoon: #2563eb;
|
||||||
|
--bar-evening: #9333ea;
|
||||||
|
--bar-night: #4f46e5;
|
||||||
|
--metric-bg: #fae8ff;
|
||||||
|
--metric-border: #f5d0fe;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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: 14px; 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); }
|
||||||
|
.stat-val { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 13px; font-weight: 700; fill: var(--text-main); }
|
||||||
|
.stat-lbl { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 10px; fill: var(--text-sub); }
|
||||||
|
.bar-lbl { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 11px; font-weight: 600; fill: var(--text-main); }
|
||||||
|
.bar-time { font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif; font-size: 9px; 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, 20)\">");
|
||||||
|
sb.AppendLine(" <text class=\"title\" x=\"0\" y=\"14\">⏱️ Coding Biorhythmus <tspan font-weight=\"400\" fill=\"var(--accent)\">•</tspan> <tspan font-weight=\"500\" font-size=\"12\" fill=\"var(--text-sub)\">Aktivitäts-Phasen (CET)</tspan></text>");
|
||||||
|
|
||||||
|
string badgeText = prod.DevArchetype;
|
||||||
|
sb.AppendLine($" <rect x=\"390\" y=\"0\" width=\"310\" height=\"22\" rx=\"11\" fill=\"var(--metric-bg)\" stroke=\"var(--metric-border)\" stroke-width=\"1\" />");
|
||||||
|
sb.AppendLine($" <circle cx=\"402\" cy=\"11\" r=\"4\" fill=\"var(--accent)\" />");
|
||||||
|
sb.AppendLine($" <text class=\"subtitle\" x=\"412\" y=\"15\" font-weight=\"600\">{badgeText}</text>");
|
||||||
|
sb.AppendLine(" </g>");
|
||||||
|
|
||||||
|
// 4 Time Distribution Bars
|
||||||
|
int barStartY = 60;
|
||||||
|
int barHeight = 16;
|
||||||
|
const double maxBarW = 430.0;
|
||||||
|
|
||||||
|
var buckets = new (string Icon, string Label, string Time, double Pct, string Color)[]
|
||||||
|
{
|
||||||
|
("🌅", "Morgen", "06:00 - 12:00", prod.MorningPct, "var(--bar-morning)"),
|
||||||
|
("☀️", "Nachmittag", "12:00 - 18:00", prod.AfternoonPct, "var(--bar-afternoon)"),
|
||||||
|
("🌆", "Abend", "18:00 - 24:00", prod.EveningPct, "var(--bar-evening)"),
|
||||||
|
("🌙", "Nacht", "00:00 - 06:00", prod.NightPct, "var(--bar-night)")
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < buckets.Length; i++)
|
||||||
|
{
|
||||||
|
var (icon, label, time, pct, color) = buckets[i];
|
||||||
|
int y = barStartY + (i * 36);
|
||||||
|
|
||||||
|
sb.AppendLine($" <!-- Bucket: {label} -->");
|
||||||
|
sb.AppendLine($" <g transform=\"translate({gridStartX}, {y})\">");
|
||||||
|
sb.AppendLine($" <text class=\"bar-lbl\" x=\"0\" y=\"12\">{icon} {label}</text>");
|
||||||
|
sb.AppendLine($" <text class=\"bar-time\" x=\"110\" y=\"12\">({time})</text>");
|
||||||
|
|
||||||
|
// Progress bar background & fill
|
||||||
|
int barX = 200;
|
||||||
|
double w = Math.Max(4.0, (pct / 100.0) * maxBarW);
|
||||||
|
sb.AppendLine($" <rect x=\"{barX}\" y=\"0\" width=\"{maxBarW}\" height=\"{barHeight}\" rx=\"4\" fill=\"var(--metric-bg)\" stroke=\"var(--metric-border)\" stroke-width=\"0.8\" />");
|
||||||
|
sb.AppendLine($" <rect x=\"{barX}\" y=\"0\" width=\"{w.ToString("F1", CultureInfo.InvariantCulture)}\" height=\"{barHeight}\" rx=\"4\" fill=\"{color}\" />");
|
||||||
|
sb.AppendLine($" <text class=\"stat-val\" x=\"{barX + maxBarW + 14}\" y=\"13\" font-size=\"12\">{pct:F1}%</text>");
|
||||||
|
sb.AppendLine(" </g>");
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("</svg>");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
private static void RenderMetricCard(StringBuilder sb, int x, int y, int w, int h, string icon, string value, string label)
|
private static void RenderMetricCard(StringBuilder sb, int x, int y, int w, int h, string icon, string value, string label)
|
||||||
{
|
{
|
||||||
sb.AppendLine($" <g transform=\"translate({x}, {y})\">");
|
sb.AppendLine($" <g transform=\"translate({x}, {y})\">");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user