diff --git a/layouts/index.html b/layouts/index.html
index 80a9fdc1..dd454241 100644
--- a/layouts/index.html
+++ b/layouts/index.html
@@ -18,6 +18,8 @@
{{ partial "features.html" . }}
+ {{ partial "tip-of-the-week.html" . }}
+
{{ partial "download.html" . }}
{{ partial "testimonials.html" . }}
diff --git a/layouts/partials/tip-of-the-week.html b/layouts/partials/tip-of-the-week.html
new file mode 100644
index 00000000..b17d0e26
--- /dev/null
+++ b/layouts/partials/tip-of-the-week.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
MISP tip of the week
+
A useful tip from the MISP community
+
Loading a tip…
+
+ Learn more
+
+
+
+
+
+
+
diff --git a/static/css/custom.css b/static/css/custom.css
index 65e268b3..52b1d9ca 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -1354,6 +1354,53 @@ ul ul {
}
}
+/* Tip of the week */
+.tip-of-the-week {
+ background: #f2f8fa;
+ padding: 58px 0;
+}
+
+.tip-of-the-week__card {
+ align-items: flex-start;
+ background: #fff;
+ border: 1px solid #d5e7eb;
+ border-left: 5px solid var(--primary-accent, #00a3b8);
+ border-radius: var(--radius-md, 8px);
+ box-shadow: var(--shadow-sm, 0 4px 15px rgba(0, 0, 0, .08));
+ display: flex;
+ gap: 24px;
+ padding: 32px 36px;
+}
+
+.tip-of-the-week__icon {
+ align-items: center;
+ background: #fff4ce;
+ border-radius: 50%;
+ color: #a96e00;
+ display: flex;
+ flex: 0 0 54px;
+ font-size: 23px;
+ height: 54px;
+ justify-content: center;
+}
+
+.tip-of-the-week__content { flex: 1 1 auto; min-width: 0; }
+.tip-of-the-week__eyebrow { color: #087f98; display: block; font-size: 12px; font-weight: 800; letter-spacing: .12em; margin-bottom: 7px; text-transform: uppercase; }
+.tip-of-the-week h2 { font-size: 24px; margin: 0 0 10px; }
+.tip-of-the-week__text { color: #536972; font-size: 16px; line-height: 1.7; margin: 0; white-space: pre-line; }
+.tip-of-the-week__link { display: inline-block; font-size: 13px; font-weight: 700; margin-top: 14px; }
+.tip-of-the-week__link i { margin-left: 6px; }
+.tip-of-the-week__refresh { background: transparent; border: 1px solid #c6dce1; border-radius: 20px; color: #087f98; flex: 0 0 auto; font-size: 13px; font-weight: 700; padding: 8px 14px; }
+.tip-of-the-week__refresh:hover, .tip-of-the-week__refresh:focus { background: #e5f5f7; border-color: #8fcbd4; }
+.tip-of-the-week__refresh i { margin-right: 6px; }
+
+@media (max-width: 767px) {
+ .tip-of-the-week { padding: 38px 0; }
+ .tip-of-the-week__card { flex-wrap: wrap; gap: 18px; padding: 26px 24px; }
+ .tip-of-the-week__content { flex-basis: calc(100% - 78px); }
+ .tip-of-the-week__refresh { margin-left: 78px; }
+}
+
/* Feature catalogue */
.features-page {
padding: 0 !important;
diff --git a/static/js/tip-of-the-week.js b/static/js/tip-of-the-week.js
new file mode 100644
index 00000000..fb90b555
--- /dev/null
+++ b/static/js/tip-of-the-week.js
@@ -0,0 +1,130 @@
+(function () {
+ "use strict";
+
+ var sourceUrl = "https://raw.githubusercontent.com/cudeso/misp-tip-of-the-week/main/misp-tip-of-the-week.json";
+ var repositoryUrl = "https://github.com/cudeso/misp-tip-of-the-week";
+ var titleElement = document.getElementById("tip-of-the-week-title");
+ var textElement = document.getElementById("tip-of-the-week-text");
+ var linkElement = document.getElementById("tip-of-the-week-link");
+ var refreshButton = document.getElementById("tip-of-the-week-refresh");
+ var tips = [];
+ var currentIndex = -1;
+
+ if (!titleElement || !textElement || !linkElement || !refreshButton) {
+ return;
+ }
+
+ function firstString(object, keys) {
+ for (var i = 0; i < keys.length; i += 1) {
+ if (typeof object[keys[i]] === "string" && object[keys[i]].trim()) {
+ return object[keys[i]].trim();
+ }
+ }
+ return "";
+ }
+
+ function normalizeTip(tip) {
+ if (typeof tip === "string") {
+ return { title: "MISP tip of the week", text: tip, url: "" };
+ }
+
+ if (!tip || typeof tip !== "object") {
+ return null;
+ }
+
+ var text = firstString(tip, ["tip", "description", "content", "text", "body"]);
+ if (!text) {
+ return null;
+ }
+
+ return {
+ title: firstString(tip, ["title", "name", "subject"]) || "MISP tip of the week",
+ text: text,
+ url: firstString(tip, ["url", "link", "reference", "source"])
+ };
+ }
+
+ function findEntries(data) {
+ if (Array.isArray(data)) {
+ return data;
+ }
+
+ if (!data || typeof data !== "object") {
+ return [];
+ }
+
+ var preferredEntries = data.tips || data.items || data.entries;
+ if (Array.isArray(preferredEntries)) {
+ return preferredEntries;
+ }
+
+ var keys = Object.keys(data);
+ for (var i = 0; i < keys.length; i += 1) {
+ if (Array.isArray(data[keys[i]])) {
+ return data[keys[i]];
+ }
+ }
+
+ return [];
+ }
+
+ function safeHttpUrl(value) {
+ if (!value) {
+ return "";
+ }
+
+ try {
+ var url = new URL(value, repositoryUrl);
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : "";
+ } catch (error) {
+ return "";
+ }
+ }
+
+ function showRandomTip() {
+ var nextIndex;
+
+ if (!tips.length) {
+ return;
+ }
+
+ do {
+ nextIndex = Math.floor(Math.random() * tips.length);
+ } while (tips.length > 1 && nextIndex === currentIndex);
+
+ currentIndex = nextIndex;
+ titleElement.textContent = tips[currentIndex].title;
+ textElement.textContent = tips[currentIndex].text;
+
+ var tipUrl = safeHttpUrl(tips[currentIndex].url);
+ linkElement.href = tipUrl || repositoryUrl;
+ linkElement.hidden = !tipUrl;
+ }
+
+ refreshButton.addEventListener("click", showRandomTip);
+
+ fetch(sourceUrl, { headers: { "Accept": "application/json" } })
+ .then(function (response) {
+ if (!response.ok) {
+ throw new Error("Tip request failed with status " + response.status);
+ }
+ return response.json();
+ })
+ .then(function (data) {
+ tips = findEntries(data).map(normalizeTip).filter(Boolean);
+
+ if (!tips.length) {
+ throw new Error("The tip feed did not contain any supported tips");
+ }
+
+ showRandomTip();
+ refreshButton.hidden = tips.length < 2;
+ })
+ .catch(function () {
+ titleElement.textContent = "MISP tip of the week";
+ textElement.textContent = "The community tip could not be loaded right now.";
+ linkElement.href = repositoryUrl;
+ linkElement.textContent = "Browse all tips";
+ linkElement.hidden = false;
+ });
+}());