From 62beee7ff805199bd28cd0607fc3c7b38c65a72b Mon Sep 17 00:00:00 2001
From: zyqunix <117040076+zyqunix@users.noreply.github.com>
Date: Mon, 23 Jun 2025 16:50:49 +0200
Subject: [PATCH] add github stats
---
assets/css/style.css | 25 +++
assets/js/github.js | 76 ++++++++
assets/js/index.js | 2 +
assets/js/wakatime.js | 441 +++++++++++++++++++++---------------------
index.html | 8 +
5 files changed, 331 insertions(+), 221 deletions(-)
create mode 100644 assets/js/github.js
diff --git a/assets/css/style.css b/assets/css/style.css
index 771fa75..d3ae1a4 100644
--- a/assets/css/style.css
+++ b/assets/css/style.css
@@ -733,3 +733,28 @@ br {
.twitter-contact > img {
color: var(--white);
}
+
+#github-full {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
+}
+
+.gitnamepfp {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+}
+
+#github-full a {
+ text-decoration: none;
+ font-weight: 400 !important;
+ color: var(--text) !important;
+}
+
+#github-full a:hover {
+ text-decoration: underline;
+ color: var(--white) !important;
+}
diff --git a/assets/js/github.js b/assets/js/github.js
new file mode 100644
index 0000000..0f08579
--- /dev/null
+++ b/assets/js/github.js
@@ -0,0 +1,76 @@
+export async function fetchGithubStats(user) {
+ const response = await fetch(`https://api.github.com/users/${user}`);
+ if (!response.ok) throw new Error(`Error fetching Github Info: ${response.statusText}`);
+ return await response.json();
+}
+
+export async function getTotalStars(username) {
+ let page = 1;
+ let totalStars = 0;
+ let hasMore = true;
+
+ while (hasMore) {
+ const response = await fetch(`https://api.github.com/users/${username}/repos?per_page=100&page=${page}`);
+ const repos = await response.json();
+
+ if (repos.length === 0) break;
+ totalStars += repos.reduce((sum, repo) => sum + repo.stargazers_count, 0);
+ hasMore = repos.length === 100;
+ page++;
+ }
+ return totalStars;
+}
+
+export async function writeGithubStats(targetId) {
+ const data = await fetchGithubStats("zyqunix");
+ const stars = await getTotalStars("zyqunix");
+
+ const target = document.querySelector(targetId);
+ target.innerHTML = "";
+
+ const mainEl = document.createElement("div");
+ mainEl.classList.add("gitnamepfp");
+
+ const pfp = document.createElement("img");
+ pfp.src = data.avatar_url;
+ pfp.style.borderRadius = "50%";
+ pfp.style.width = "96px";
+
+ const name = document.createElement("a");
+ name.innerText = data.login;
+ name.href = data.html_url;
+ name.target = "_blank";
+ name.style.fontSize = "20px";
+ name.classList.add("tooltip");
+ name.setAttribute("data-tooltip", data.bio);
+
+ const pubRepos = document.createElement("a");
+ pubRepos.innerText = `${data.public_repos} Public Repositories`;
+ pubRepos.href = `https://github.com/${data.login}?tab=repos`;
+ pubRepos.id = "pubrepos";
+
+ const followers = document.createElement("div");
+ followers.innerHTML = `${data.followers} Followers & Following ${data.following}`;
+
+
+ const hireable = document.createElement("div");
+ if (data.hireable === "null") {
+ hireable.innerText = "Not Hireable";
+ } else {
+ hireable.innerText = "Hire Me!";
+ }
+
+ const tStars = document.createElement("div");
+ tStars.innerText = `${stars} Total Stars`;
+
+ const registered = data.created_at;
+ document.getElementById("gh_since").innerText = `Registed on ${registered.slice(0, 10).replace(/-/g, "/")}`;
+
+ mainEl.appendChild(pfp);
+ mainEl.appendChild(name);
+ target.appendChild(mainEl);
+ target.appendChild(pubRepos);
+ target.appendChild(followers);
+ target.appendChild(hireable);
+ target.appendChild(tStars);
+}
diff --git a/assets/js/index.js b/assets/js/index.js
index f039d7e..3ccb74a 100644
--- a/assets/js/index.js
+++ b/assets/js/index.js
@@ -1,4 +1,5 @@
import * as wakatime from "./wakatime.js";
+import * as github from "./github.js";
const timeElem = document.getElementById('time');
const timezone = 'Europe/Berlin';
@@ -241,6 +242,7 @@ function fetchWeather(location) {
}
wakatime.fetchWakatime("#wakapi");
+github.writeGithubStats("#github-full");
const messages = [
"Coding",
diff --git a/assets/js/wakatime.js b/assets/js/wakatime.js
index 3a31e89..9f9f422 100644
--- a/assets/js/wakatime.js
+++ b/assets/js/wakatime.js
@@ -57,7 +57,7 @@ const projectColors = [
'#E2F0CB',
'#F5E6E8',
'#C9D6DF'
-];
+];
const categoryColors = {
coding: '#A8DADC',
@@ -66,274 +66,273 @@ const categoryColors = {
};
export async function fetchWakaTimeStats(user, range) {
- const response = await fetch(`https://wakapi.atums.world/api/v1/users/${user}/stats/${range}`);
- if (!response.ok) throw new Error(`Error fetching WakaTime stats: ${response.statusText}`);
- return await response.json();
+ const response = await fetch(`https://wakapi.atums.world/api/v1/users/${user}/stats/${range}`);
+ if (!response.ok) throw new Error(`Error fetching WakaTime stats: ${response.statusText}`);
+ return await response.json();
}
export async function prepareChartData() {
- const languages = await fetchWakaTimeStats("zyqunix", "all_time");
- const sortedLanguages = [...languages.data.languages].sort((a, b) => b.percent - a.percent);
+ const languages = await fetchWakaTimeStats("zyqunix", "all_time");
+ const sortedLanguages = [...languages.data.languages].sort((a, b) => b.percent - a.percent);
- const totalSeconds = sortedLanguages.reduce((total, lang) => total + lang.total_seconds, 0);
- let totalTime = '';
+ const totalSeconds = sortedLanguages.reduce((total, lang) => total + lang.total_seconds, 0);
+ let totalTime = '';
- if (totalSeconds > 3600) {
- totalTime = `${Math.floor(totalSeconds / 3600)}h ${Math.floor((totalSeconds % 3600) / 60)}m`;
- } else {
- totalTime = `${Math.floor(totalSeconds / 60)}m`;
- }
-
- const limit = 10;
- const topLanguages = sortedLanguages.slice(0, limit);
-
- if (sortedLanguages.length > limit) {
- const otherSeconds = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.total_seconds, 0);
- const otherPercent = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.percent, 0);
-
- let otherText = '';
- if (otherSeconds > 3600) {
- otherText = `${Math.floor(otherSeconds / 3600)}h ${Math.floor((otherSeconds % 3600) / 60)}m`;
+ if (totalSeconds > 3600) {
+ totalTime = `${Math.floor(totalSeconds / 3600)}h ${Math.floor((totalSeconds % 3600) / 60)}m`;
} else {
- otherText = `${Math.floor(otherSeconds / 60)}m`;
+ totalTime = `${Math.floor(totalSeconds / 60)}m`;
}
- topLanguages.push({
- name: 'Other',
- total_seconds: otherSeconds,
- percent: otherPercent,
- text: otherText,
- color: '#CCCCCC',
- digital: '',
- hours: Math.floor(otherSeconds / 3600),
- minutes: Math.floor((otherSeconds % 3600) / 60),
- seconds: otherSeconds % 60
- });
- }
+ const limit = 10;
+ const topLanguages = sortedLanguages.slice(0, limit);
- const segmentsWithColors = topLanguages.map(lang => ({
- ...lang,
- color: lang.color || langColors[lang.name] || '#CCCCCC'
- }));
+ if (sortedLanguages.length > limit) {
+ const otherSeconds = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.total_seconds, 0);
+ const otherPercent = sortedLanguages.slice(limit).reduce((total, lang) => total + lang.percent, 0);
- return {
- segments: segmentsWithColors,
- totalTime
- };
+ let otherText = '';
+ if (otherSeconds > 3600) {
+ otherText = `${Math.floor(otherSeconds / 3600)}h ${Math.floor((otherSeconds % 3600) / 60)}m`;
+ } else {
+ otherText = `${Math.floor(otherSeconds / 60)}m`;
+ }
+
+ topLanguages.push({
+ name: 'Other',
+ total_seconds: otherSeconds,
+ percent: otherPercent,
+ text: otherText,
+ color: '#CCCCCC',
+ digital: '',
+ hours: Math.floor(otherSeconds / 3600),
+ minutes: Math.floor((otherSeconds % 3600) / 60),
+ seconds: otherSeconds % 60
+ });
+ }
+
+ const segmentsWithColors = topLanguages.map(lang => ({
+ ...lang,
+ color: lang.color || langColors[lang.name] || '#CCCCCC'
+ }));
+
+ return {
+ segments: segmentsWithColors,
+ totalTime
+ };
}
export async function fetchWakatime(targetId) {
- const data = await fetchWakaTimeStats("zyqunix", "all_time");
+ const data = await fetchWakaTimeStats("zyqunix", "all_time");
const target = document.querySelector(`${targetId}`);
target.innerHTML = "";
- const langDetails = document.createElement("details");
- const langSummary = document.createElement("summary");
- langSummary.innerText = "Languages";
- langSummary.classList.add("tooltip");
- langSummary.setAttribute("data-tooltip", "Most Used Languages");
- langDetails.appendChild(langSummary);
- langDetails.style.marginTop = "15px";
- target.appendChild(langDetails);
+ const langDetails = document.createElement("details");
+ const langSummary = document.createElement("summary");
+ langSummary.innerText = "Languages";
+ langSummary.classList.add("tooltip");
+ langSummary.setAttribute("data-tooltip", "Most Used Languages");
+ langDetails.appendChild(langSummary);
+ langDetails.style.marginTop = "15px";
+ target.appendChild(langDetails);
- const topLangs = data.data.languages.slice(0, 10);
- topLangs.forEach(lang => {
- const el = document.createElement("div");
- el.innerText = `${lang.name}: ${lang.text}`;
- el.id = lang.name.toLowerCase();
- el.classList.add("proglang");
- el.style.margin = "5px";
- el.style.padding = "10px";
- el.style.borderRadius = "5px";
- el.style.backgroundColor = langColors[lang.name];
- el.style.color = "var(--base)";
- langDetails.appendChild(el);
- });
+ const topLangs = data.data.languages.slice(0, 10);
+ topLangs.forEach(lang => {
+ const el = document.createElement("div");
+ el.innerText = `${lang.name}: ${lang.text}`;
+ el.id = lang.name.toLowerCase();
+ el.classList.add("proglang");
+ el.style.margin = "5px";
+ el.style.padding = "10px";
+ el.style.borderRadius = "5px";
+ el.style.backgroundColor = langColors[lang.name];
+ el.style.color = "var(--base)";
+ langDetails.appendChild(el);
+ });
- const edDetails = document.createElement("details");
- const edSummary = document.createElement("summary");
- edSummary.innerText = "Editors";
- edSummary.classList.add("tooltip");
- edSummary.setAttribute("data-tooltip", "Most Used Editors");
- edDetails.appendChild(edSummary);
- edDetails.style.marginTop = "15px";
- target.appendChild(edDetails);
+ const edDetails = document.createElement("details");
+ const edSummary = document.createElement("summary");
+ edSummary.innerText = "Editors";
+ edSummary.classList.add("tooltip");
+ edSummary.setAttribute("data-tooltip", "Most Used Editors");
+ edDetails.appendChild(edSummary);
+ edDetails.style.marginTop = "15px";
+ target.appendChild(edDetails);
- const topEditors = data.data.editors.slice(0, 5);
- topEditors.forEach(editor => {
- const el = document.createElement("div");
- el.innerText = `In ${editor.name} for ${editor.text} (${editor.percent}%)`;
- el.style.margin = "5px";
- el.style.padding = "10px";
- el.style.borderRadius = "5px";
- el.style.color = "var(--base)";
- el.style.backgroundColor = editorColors[editor.name.toLowerCase().replace(/\s+/g, '').replace(/[^a-zA-Z]/g, '')];
- edDetails.appendChild(el);
- });
+ const topEditors = data.data.editors.slice(0, 5);
+ topEditors.forEach(editor => {
+ const el = document.createElement("div");
+ el.innerText = `In ${editor.name} for ${editor.text} (${editor.percent}%)`;
+ el.style.margin = "5px";
+ el.style.padding = "10px";
+ el.style.borderRadius = "5px";
+ el.style.color = "var(--base)";
+ el.style.backgroundColor = editorColors[editor.name.toLowerCase().replace(/\s+/g, '').replace(/[^a-zA-Z]/g, '')];
+ edDetails.appendChild(el);
+ });
- const prDetails = document.createElement("details");
- const prSummary = document.createElement("summary");
- prSummary.innerText = "Projects";
- prSummary.classList.add("tooltip");
- prSummary.setAttribute("data-tooltip", "Most Used Projects");
- prDetails.appendChild(prSummary);
- prDetails.style.marginTop = "15px";
- target.appendChild(prDetails);
+ const prDetails = document.createElement("details");
+ const prSummary = document.createElement("summary");
+ prSummary.innerText = "Projects";
+ prSummary.classList.add("tooltip");
+ prSummary.setAttribute("data-tooltip", "Most Used Projects");
+ prDetails.appendChild(prSummary);
+ prDetails.style.marginTop = "15px";
+ target.appendChild(prDetails);
- const topProjects = data.data.projects.slice(0, 10);
- topProjects.forEach(project => {
- const el = document.createElement("div");
- el.innerText = `Coded ${project.name} for ${project.text}`;
- el.style.margin = "5px";
- el.style.padding = "10px";
- el.style.borderRadius = "5px";
- el.style.color = "var(--base)";
- el.style.backgroundColor = projectColors[Math.floor(Math.random() * projectColors.length)];
- prDetails.appendChild(el);
- });
+ const topProjects = data.data.projects.slice(0, 10);
+ topProjects.forEach(project => {
+ const el = document.createElement("div");
+ el.innerText = `Coded ${project.name} for ${project.text}`;
+ el.style.margin = "5px";
+ el.style.padding = "10px";
+ el.style.borderRadius = "5px";
+ el.style.color = "var(--base)";
+ el.style.backgroundColor = projectColors[Math.floor(Math.random() * projectColors.length)];
+ prDetails.appendChild(el);
+ });
- const osDetails = document.createElement("details");
- const osSummary = document.createElement("summary");
- osSummary.innerText = "Operating Systems";
- osSummary.classList.add("tooltip");
- osSummary.setAttribute("data-tooltip", "Most Used Operating Systems");
- osDetails.appendChild(osSummary);
- osDetails.style.marginTop = "15px";
- target.appendChild(osDetails);
+ const osDetails = document.createElement("details");
+ const osSummary = document.createElement("summary");
+ osSummary.innerText = "Operating Systems";
+ osSummary.classList.add("tooltip");
+ osSummary.setAttribute("data-tooltip", "Most Used Operating Systems");
+ osDetails.appendChild(osSummary);
+ osDetails.style.marginTop = "15px";
+ target.appendChild(osDetails);
- const topOS = data.data.operating_systems;
- topOS.forEach(machine => {
- const el = document.createElement('div');
- el.innerText = `Coded on ${machine.name} for ${machine.text}`;
- el.style.margin = "5px";
- el.style.padding = "10px";
- el.style.borderRadius = "5px";
- el.style.color = "var(--base)";
- el.style.backgroundColor = osColors[machine.name.toLowerCase()];
- osDetails.appendChild(el);
- });
+ const topOS = data.data.operating_systems;
+ topOS.forEach(machine => {
+ const el = document.createElement('div');
+ el.innerText = `Coded on ${machine.name} for ${machine.text}`;
+ el.style.margin = "5px";
+ el.style.padding = "10px";
+ el.style.borderRadius = "5px";
+ el.style.color = "var(--base)";
+ el.style.backgroundColor = osColors[machine.name.toLowerCase()];
+ osDetails.appendChild(el);
+ });
- const caDetails = document.createElement("details");
- const caSummary = document.createElement("summary");
- caSummary.innerText = "Categories";
- caSummary.classList.add("tooltip");
- caSummary.setAttribute("data-tooltip", "Time Spent by Category");
- caDetails.appendChild(caSummary);
- caDetails.style.marginTop = "15px";
- target.appendChild(caDetails);
+ const caDetails = document.createElement("details");
+ const caSummary = document.createElement("summary");
+ caSummary.innerText = "Categories";
+ caSummary.classList.add("tooltip");
+ caSummary.setAttribute("data-tooltip", "Time Spent by Category");
+ caDetails.appendChild(caSummary);
+ caDetails.style.marginTop = "15px";
+ target.appendChild(caDetails);
- const categories = data.data.categories;
- categories.forEach(category => {
- const el = document.createElement('div');
- el.style.margin = "5px";
- el.style.padding = "10px";
- el.style.borderRadius = "5px";
- el.style.color = "var(--base)";
- el.style.backgroundColor = categoryColors[category.name.toLowerCase()];
- el.innerText = `Has done ${category.name} for ${category.text}`;
- caDetails.appendChild(el);
- });
+ const categories = data.data.categories;
+ categories.forEach(category => {
+ const el = document.createElement('div');
+ el.style.margin = "5px";
+ el.style.padding = "10px";
+ el.style.borderRadius = "5px";
+ el.style.color = "var(--base)";
+ el.style.backgroundColor = categoryColors[category.name.toLowerCase()];
+ el.innerText = `Has done ${category.name} for ${category.text}`;
+ caDetails.appendChild(el);
+ });
- const miscDetails = document.createElement("details");
- const miscSummary = document.createElement("summary");
- miscSummary.innerText = "Miscellaneous";
- miscSummary.classList.add("tooltip");
- miscSummary.setAttribute("data-tooltip", "Miscellaneous Wakatime Info");
- miscDetails.appendChild(miscSummary);
- miscDetails.style.marginTop = "15px";
- target.appendChild(miscDetails);
+ const miscDetails = document.createElement("details");
+ const miscSummary = document.createElement("summary");
+ miscSummary.innerText = "Miscellaneous";
+ miscSummary.classList.add("tooltip");
+ miscSummary.setAttribute("data-tooltip", "Miscellaneous Wakatime Info");
+ miscDetails.appendChild(miscSummary);
+ miscDetails.style.marginTop = "15px";
+ target.appendChild(miscDetails);
- const el = document.createElement("div");
- el.innerHTML = `
+ const el = document.createElement("div");
+ el.innerHTML = `
Total Coding Time: ${data.data.human_readable_total}
Daily Average: ${data.data.human_readable_daily_average}
Days Since Register: ${data.data.days_including_holidays}
`;
- miscDetails.appendChild(el);
+ miscDetails.appendChild(el);
- document.getElementById("stats_since").innerText = `Registered on ${data.data.start}`;
+ const registered = data.data.start;
+ document.getElementById("stats_since").innerText = `Registered on ${registered.slice(0, 10).replace(/-/g, "/")}`;
- const chartDetails = document.createElement("details");
- const chartSummary = document.createElement("summary");
- chartSummary.innerText = "Chart";
- chartSummary.classList.add("tooltip");
- chartSummary.setAttribute("data-tooltip", "Miscellaneous Coding Info");
- chartDetails.appendChild(chartSummary);
- chartDetails.style.marginTop = "15px";
- target.appendChild(chartDetails);
+ const chartDetails = document.createElement("details");
+ const chartSummary = document.createElement("summary");
+ chartSummary.innerText = "Chart";
+ chartSummary.classList.add("tooltip");
+ chartSummary.setAttribute("data-tooltip", "Miscellaneous Coding Info");
+ chartDetails.appendChild(chartSummary);
+ chartDetails.style.marginTop = "15px";
+ target.appendChild(chartDetails);
- const chartData = await prepareChartData();
+ const chartData = await prepareChartData();
-
- const svgNS = "http://www.w3.org/2000/svg";
- const radius = 50;
- const center = 60;
- const strokeWidth = 20;
- const circumference = 2 * Math.PI * radius;
- const container = document.createElement("div");
+ const svgNS = "http://www.w3.org/2000/svg";
+ const radius = 50;
+ const center = 60;
+ const strokeWidth = 20;
+ const circumference = 2 * Math.PI * radius;
- const legend = document.createElement("div");
- legend.style.display = 'flex';
- legend.style.flexDirection = 'column';
- legend.style.gap = '8px';
- legend.id = 'legend';
+ const container = document.createElement("div");
- chartData.segments.forEach(segment => {
- const label = document.createElement('div');
- label.style.display = 'flex';
- label.style.alignItems = 'center';
- label.style.gap = '8px';
-
- const colorBox = document.createElement('span');
- colorBox.style.width = '10px';
- colorBox.style.height = '10px';
- colorBox.style.backgroundColor = segment.color;
- colorBox.style.borderRadius = '3px';
-
- const text = document.createElement('span');
- text.innerText = `${segment.name} (${segment.text})`;
-
- label.appendChild(colorBox);
- label.appendChild(text);
- legend.appendChild(label);
- });
+ const legend = document.createElement("div");
+ legend.style.display = 'flex';
+ legend.style.flexDirection = 'column';
+ legend.style.gap = '8px';
+ legend.id = 'legend';
- const chart = document.createElementNS(svgNS, "svg");
- chart.setAttribute("viewBox", "0 0 120 120");
- chart.style.width = "120px";
- chart.style.height = "120px";
+ chartData.segments.forEach(segment => {
+ const label = document.createElement('div');
+ label.style.display = 'flex';
+ label.style.alignItems = 'center';
+ label.style.gap = '8px';
- let cumulativePercent = 0;
+ const colorBox = document.createElement('span');
+ colorBox.style.width = '10px';
+ colorBox.style.height = '10px';
+ colorBox.style.backgroundColor = segment.color;
+ colorBox.style.borderRadius = '3px';
- chartData.segments.forEach(segment => {
- const circle = document.createElementNS(svgNS, "circle");
- circle.setAttribute("cx", center);
- circle.setAttribute("cy", center);
- circle.setAttribute("r", radius);
- circle.setAttribute("fill", "none");
- circle.setAttribute("stroke", segment.color);
- circle.setAttribute("stroke-width", strokeWidth);
- circle.setAttribute("transform", `rotate(-90 ${center} ${center})`);
+ const text = document.createElement('span');
+ text.innerText = `${segment.name} (${segment.text})`;
- const segmentLength = (segment.percent / 100) * circumference;
- const emptyLength = circumference - segmentLength;
+ label.appendChild(colorBox);
+ label.appendChild(text);
+ legend.appendChild(label);
+ });
- circle.setAttribute("stroke-dasharray", `${segmentLength} ${emptyLength}`);
- circle.setAttribute("stroke-dashoffset", circumference * (1 - cumulativePercent / 100));
+ const chart = document.createElementNS(svgNS, "svg");
+ chart.setAttribute("viewBox", "0 0 120 120");
+ chart.style.width = "120px";
+ chart.style.height = "120px";
- cumulativePercent += segment.percent;
+ let cumulativePercent = 0;
- chart.appendChild(circle);
- });
+ chartData.segments.forEach(segment => {
+ const circle = document.createElementNS(svgNS, "circle");
+ circle.setAttribute("cx", center);
+ circle.setAttribute("cy", center);
+ circle.setAttribute("r", radius);
+ circle.setAttribute("fill", "none");
+ circle.setAttribute("stroke", segment.color);
+ circle.setAttribute("stroke-width", strokeWidth);
+ circle.setAttribute("transform", `rotate(-90 ${center} ${center})`);
- container.appendChild(legend);
- container.appendChild(chart);
+ const segmentLength = (segment.percent / 100) * circumference;
+ const emptyLength = circumference - segmentLength;
- chartDetails.appendChild(container);
+ circle.setAttribute("stroke-dasharray", `${segmentLength} ${emptyLength}`);
+ circle.setAttribute("stroke-dashoffset", circumference * (1 - cumulativePercent / 100));
+
+ cumulativePercent += segment.percent;
+
+ chart.appendChild(circle);
+ });
+
+ container.appendChild(legend);
+ container.appendChild(chart);
+
+ chartDetails.appendChild(container);
}
-
-console.log(await fetchWakaTimeStats("zyqunix", "all_time"));
diff --git a/index.html b/index.html
index 8b88b0a..1790a54 100644
--- a/index.html
+++ b/index.html
@@ -43,6 +43,14 @@
+
+
GitHub Stats
+
Since 30 Oct 2022
+
+
+
+
+