From 05a8324c98300f44d94a326515a19dac1fd27651 Mon Sep 17 00:00:00 2001
From: Alex Savelyev <91429106+alexdln@users.noreply.github.com>
Date: Mon, 25 May 2026 15:01:28 +0100
Subject: [PATCH] feat: add nodejs noodle (#2778)

---
 app/components/Noodle/Nodejs/Logo.vue | 89 +++++++++++++++++++++++++++
 app/components/Noodle/Press/Logo.vue  | 19 ------
 app/components/Noodle/index.ts        | 10 ++-
 app/components/Tooltip/App.vue        |  4 +-
 knip.ts                               |  1 +
 test/nuxt/a11y.spec.ts                |  4 +-
 6 files changed, 99 insertions(+), 28 deletions(-)
 create mode 100644 app/components/Noodle/Nodejs/Logo.vue
 delete mode 100644 app/components/Noodle/Press/Logo.vue

diff --git a/app/components/Noodle/Nodejs/Logo.vue b/app/components/Noodle/Nodejs/Logo.vue
new file mode 100644
index 0000000000..39166f5859
--- /dev/null
+++ b/app/components/Noodle/Nodejs/Logo.vue
@@ -0,0 +1,89 @@
+<template>
+  <TooltipApp interactive :tooltipAttr="{ class: 'w-[calc(100%-2rem)]! max-w-sm!' }">
+    <template #content>
+      <p class="text-sm font-medium text-fg mb-1">
+        <span class="text-fg-subtle">
+          // 27 May 2009 - nodejs Initial release
+          <br />
+          //&nbsp;<a
+            href="https://github.com/nodejs/node-v0.x-archive/releases/tag/v0.0.1"
+            target="_blank"
+            rel="noopener noreferrer"
+            class="underline"
+            >github.com/nodejs/node-v0.x-archive/releases/tag/v0.0.1</a
+          >
+        </span>
+        <br />
+        <span class="text-badge-yellow">console</span>.<span class="text-badge-blue">log(</span
+        ><span class="text-badge-green">"Happy birthday, nodejs"</span
+        ><span class="text-badge-blue">)</span>
+      </p>
+    </template>
+    <svg
+      width="962"
+      height="292"
+      viewBox="0 0 962 292"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      :alt="$t('alt_logo')"
+      class="mb-8 mx-auto w-72 h-auto sm:w-92 max-w-full"
+    >
+      <path
+        d="M269 198.684V86.2912L329 46.1509L389 86.2912V198.684L353 174.6V106.361L329 90.3053L305 106.361V174.6L269 198.684Z"
+        fill="currentColor"
+        stroke="currentColor"
+        stroke-width="5"
+        stroke-linejoin="round"
+      />
+      <path
+        d="M833 194.67V154.53L958 46.1509V86.2912L833 194.67Z"
+        fill="currentColor"
+        stroke="currentColor"
+        stroke-width="5"
+        stroke-linejoin="round"
+      />
+      <path
+        d="M958 194.67V154.53L833 46.1509V86.2912L958 194.67Z"
+        fill="currentColor"
+        stroke="currentColor"
+        stroke-width="5"
+        stroke-linejoin="round"
+      />
+      <path
+        d="M589 198.684V86.2912L649 46.1509L691 74.2491L733 46.1509L793 86.2912V174.6L757 198.684V106.361L733 90.3053L709 106.361V174.6L673 198.684V174.6V106.361L649 90.3053L625 106.361V174.6L589 198.684Z"
+        fill="currentColor"
+      />
+      <path
+        d="M709 174.6H673V106.361L649 90.3053L625 106.361V174.6L589 198.684V86.2912L649 46.1509L709 86.2912V174.6ZM709 174.6L673 198.684V86.2912L733 46.1509L793 86.2912V174.6L757 198.684V106.361L733 90.3053L709 106.361V174.6Z"
+        stroke="currentColor"
+        stroke-width="5"
+        stroke-linejoin="round"
+      />
+      <path
+        fill-rule="evenodd"
+        clip-rule="evenodd"
+        d="M429 86.2912V150.516V289L465 264.916L465 174.6L489 190.656L513 174.6L549 150.516V86.2912L489 46.1509L429 86.2912ZM513 130.446V106.361L489 90.3053L465 106.361L465 130.446L489 146.502L513 130.446Z"
+        fill="currentColor"
+        stroke="currentColor"
+        stroke-width="5"
+        stroke-linejoin="round"
+      />
+      <path
+        fill-rule="evenodd"
+        clip-rule="evenodd"
+        d="M52 158.544V182.628L28 198.684L4 182.628L4 158.544L28 142.488L52 158.544Z"
+        fill="#689F63"
+        stroke="#689F63"
+        stroke-width="5"
+        stroke-linejoin="round"
+      />
+      <path
+        d="M187 3H228L113 223.772H72L187 3Z"
+        fill="#689F63"
+        stroke="#689F63"
+        stroke-width="5"
+        stroke-linejoin="round"
+      />
+    </svg>
+  </TooltipApp>
+</template>
diff --git a/app/components/Noodle/Press/Logo.vue b/app/components/Noodle/Press/Logo.vue
deleted file mode 100644
index 97a078acaa..0000000000
--- a/app/components/Noodle/Press/Logo.vue
+++ /dev/null
@@ -1,19 +0,0 @@
-<template>
-  <TooltipApp interactive position="top">
-    <template #content>
-      <p class="text-sm font-medium text-fg mb-1">
-        A free press makes openness possible for everyone.
-        <LinkBase to="https://en.wikipedia.org/wiki/World_Press_Freedom_Day"
-          >Read more about Press Freedom Day</LinkBase
-        >
-      </p>
-    </template>
-    <ColorSchemeImg
-      width="320"
-      class="mb-16 w-80 sm:w-140 max-w-full"
-      dark-src="/extra/npmx-dark-press.png"
-      light-src="/extra/npmx-light-press.png"
-      :alt="$t('alt_logo')"
-    />
-  </TooltipApp>
-</template>
diff --git a/app/components/Noodle/index.ts b/app/components/Noodle/index.ts
index abeef9a132..e057d9af39 100644
--- a/app/components/Noodle/index.ts
+++ b/app/components/Noodle/index.ts
@@ -1,5 +1,5 @@
 import NoodleKawaiiLogo from './Kawaii/Logo.vue'
-import NoodlePressLogo from './Press/Logo.vue'
+import NoodleNodejsLogo from './Nodejs/Logo.vue'
 
 export type Noodle = {
   // Unique identifier for the noodle
@@ -28,11 +28,9 @@ export const PERMANENT_NOODLES: Noodle[] = [
 // Active noodles - shown based on date and timezone
 export const ACTIVE_NOODLES: Noodle[] = [
   {
-    key: 'press',
-    logo: NoodlePressLogo,
-    date: '2026-05-01',
-    dateTo: '2026-05-04',
+    key: 'nodejs',
+    logo: NoodleNodejsLogo,
+    date: '2026-05-27',
     timezone: 'auto',
-    tagline: false,
   },
 ]
diff --git a/app/components/Tooltip/App.vue b/app/components/Tooltip/App.vue
index 8b16f20482..3df16f5032 100644
--- a/app/components/Tooltip/App.vue
+++ b/app/components/Tooltip/App.vue
@@ -12,6 +12,8 @@ const props = defineProps<{
   defer?: boolean
   /** Offset distance in pixels (default: 4) */
   offset?: number
+  /** Additional attributes to be applied to the tooltip element */
+  tooltipAttr?: Record<string, unknown>
 }>()
 
 const isVisible = shallowRef(false)
@@ -38,7 +40,7 @@ function hide() {
 }
 
 const tooltipAttrs = computed(() => {
-  const attrs: Record<string, unknown> = { role: 'tooltip', id: tooltipId }
+  const attrs: Record<string, unknown> = { role: 'tooltip', id: tooltipId, ...props.tooltipAttr }
   if (props.interactive) {
     attrs.onMouseenter = show
     attrs.onMouseleave = hide
diff --git a/knip.ts b/knip.ts
index db09e209c1..f4adb33651 100644
--- a/knip.ts
+++ b/knip.ts
@@ -45,6 +45,7 @@ const config: KnipConfig = {
       ignoreFiles: [
         'app/components/Tooltip/Announce.vue',
         'app/components/UserCombobox.vue',
+        'app/components/ColorScheme/Img.vue',
         '**/*.unused.*',
       ],
     },
diff --git a/test/nuxt/a11y.spec.ts b/test/nuxt/a11y.spec.ts
index 9af1358dd9..15de17729e 100644
--- a/test/nuxt/a11y.spec.ts
+++ b/test/nuxt/a11y.spec.ts
@@ -158,7 +158,7 @@ import {
   ButtonBase,
   LandingIntroHeader,
   NoodleKawaiiLogo,
-  NoodlePressLogo,
+  NoodleNodejsLogo,
   LinkBase,
   CallToAction,
   CodeDirectoryListing,
@@ -372,7 +372,7 @@ describe('component accessibility audits', () => {
     })
 
     it('should have no accessibility violations', async () => {
-      const component = await mountSuspended(NoodlePressLogo)
+      const component = await mountSuspended(NoodleNodejsLogo)
       const results = await runAxe(component)
       expect(results.violations).toEqual([])
     })
