diff --git a/client/src/components/common/Fixed/Fixed.jsx b/client/src/components/common/Fixed/Fixed.jsx
index b57caefa..5cb44db7 100644
--- a/client/src/components/common/Fixed/Fixed.jsx
+++ b/client/src/components/common/Fixed/Fixed.jsx
@@ -1,5 +1,5 @@
/*!
- * Copyright (c) 2024 PLANKA Software GmbH
+ * Copyright (c) 2026 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
@@ -8,6 +8,7 @@ import { useSelector } from 'react-redux';
import selectors from '../../../selectors';
import Header from '../Header';
+import PromoBanner from '../PromoBanner/PromoBanner';
import Favorites from '../Favorites';
import HomeActions from '../HomeActions';
import Project from '../../projects/Project';
@@ -22,6 +23,7 @@ const Fixed = React.memo(() => {
return (
+
{projectId === undefined &&
}
{projectId &&
}
diff --git a/client/src/components/common/PromoBanner/PromoBanner.jsx b/client/src/components/common/PromoBanner/PromoBanner.jsx
new file mode 100644
index 00000000..fb94c6ed
--- /dev/null
+++ b/client/src/components/common/PromoBanner/PromoBanner.jsx
@@ -0,0 +1,111 @@
+/*!
+ * Copyright (c) 2026 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import { useSelector } from 'react-redux';
+import { useTranslation } from 'react-i18next';
+
+import selectors from '../../../selectors';
+
+import styles from './PromoBanner.module.scss';
+
+const PRO_URL = 'https://planka.app/pro?ref=app-banner';
+const DISMISS_DURATION_MS = 30 * 24 * 60 * 60 * 1000;
+const CYCLE_INTERVAL_MS = 8000;
+const CSS_VAR = '--promo-banner-height';
+
+const FEATURES = ['proFeatureCalendar', 'proFeatureRecurringCards', 'proFeatureGuestRoles'];
+
+// Alternates: main, sub1, main, sub2, main, sub3
+const TEXTS = [
+ 'discoverPlankaPro',
+ FEATURES[0],
+ 'discoverPlankaPro',
+ FEATURES[1],
+ 'discoverPlankaPro',
+ FEATURES[2],
+];
+
+function getDismissKey(userId) {
+ return `planka_proBannerDismissed_${userId}`;
+}
+
+function isBannerDismissed(userId) {
+ const stored = localStorage.getItem(getDismissKey(userId));
+ if (!stored) return false;
+ return Date.now() - Date.parse(stored) < DISMISS_DURATION_MS;
+}
+
+const PromoBanner = React.memo(() => {
+ const userId = useSelector(selectors.selectCurrentUserId);
+
+ const [dismissed, setDismissed] = useState(() => isBannerDismissed(userId));
+ const [textIndex, setTextIndex] = useState(0);
+ const [visible, setVisible] = useState(true);
+
+ const wrapperRef = useRef(null);
+ const [t] = useTranslation();
+
+ useEffect(() => {
+ if (dismissed) {
+ document.documentElement.style.removeProperty(CSS_VAR);
+ return undefined;
+ }
+
+ const height = wrapperRef.current ? wrapperRef.current.offsetHeight : 0;
+ document.documentElement.style.setProperty(CSS_VAR, `${height}px`);
+
+ return () => document.documentElement.style.removeProperty(CSS_VAR);
+ }, [dismissed]);
+
+ useEffect(() => {
+ if (dismissed) return undefined;
+
+ const interval = setInterval(() => {
+ setVisible(false);
+ setTimeout(() => {
+ setTextIndex((i) => (i + 1) % TEXTS.length);
+ setVisible(true);
+ }, 400);
+ }, CYCLE_INTERVAL_MS);
+
+ return () => clearInterval(interval);
+ }, [dismissed]);
+
+ const handleDismiss = useCallback(
+ (e) => {
+ e.preventDefault();
+ localStorage.setItem(getDismissKey(userId), new Date().toISOString());
+ setDismissed(true);
+ },
+ [userId],
+ );
+
+ if (dismissed) return null;
+
+ return (
+
+ );
+});
+
+export default PromoBanner;
diff --git a/client/src/components/common/PromoBanner/PromoBanner.module.scss b/client/src/components/common/PromoBanner/PromoBanner.module.scss
new file mode 100644
index 00000000..032bfaeb
--- /dev/null
+++ b/client/src/components/common/PromoBanner/PromoBanner.module.scss
@@ -0,0 +1,60 @@
+/*!
+ * Copyright (c) 2026 PLANKA Software GmbH
+ * Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
+ */
+
+:global(#app) {
+ .wrapper {
+ align-items: center;
+ background: rgba(0, 0, 0, 0.18);
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+ padding: 5px 16px;
+ }
+
+ .textLink {
+ color: #fff;
+ cursor: pointer;
+ flex: 1 1 auto;
+ font-size: 13px;
+ text-align: center;
+ text-decoration: none;
+
+ &:hover {
+ color: #bdff22;
+ }
+ }
+
+ .externalIcon {
+ font-size: 11px;
+ margin-left: 4px;
+ opacity: 0.7;
+ vertical-align: super;
+ }
+
+ .textVisible {
+ opacity: 1;
+ transition: opacity 0.4s ease;
+ }
+
+ .textHidden {
+ opacity: 0;
+ transition: opacity 0.4s ease;
+ }
+
+ .closeButton {
+ background: transparent;
+ border: none;
+ color: rgba(255, 255, 255, 0.45);
+ cursor: pointer;
+ flex: 0 0 auto;
+ font-size: 18px;
+ line-height: 1;
+ padding: 0 2px;
+ }
+
+ .closeButton:hover {
+ color: rgba(255, 255, 255, 0.9);
+ }
+}
diff --git a/client/src/components/common/Static/Static.module.scss b/client/src/components/common/Static/Static.module.scss
index a9fd9268..c6424457 100644
--- a/client/src/components/common/Static/Static.module.scss
+++ b/client/src/components/common/Static/Static.module.scss
@@ -1,5 +1,5 @@
/*!
- * Copyright (c) 2024 PLANKA Software GmbH
+ * Copyright (c) 2026 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
@@ -37,15 +37,15 @@
.wrapper {
height: 100%;
- margin-top: 116px;
+ margin-top: calc(116px + var(--promo-banner-height, 0px));
}
.wrapperBoard {
- margin-top: 174px;
+ margin-top: calc(174px + var(--promo-banner-height, 0px));
}
.wrapperBoardWithFavorites {
- margin-top: 264px;
+ margin-top: calc(264px + var(--promo-banner-height, 0px));
}
.wrapperFlex {
@@ -57,11 +57,11 @@
}
.wrapperProject {
- margin-top: 98px;
+ margin-top: calc(98px + var(--promo-banner-height, 0px));
}
.wrapperProjectWithFavorites {
- margin-top: 188px;
+ margin-top: calc(188px + var(--promo-banner-height, 0px));
}
.wrapperTransitioning {
@@ -73,6 +73,6 @@
}
.wrapperWithFavorites {
- margin-top: 206px;
+ margin-top: calc(206px + var(--promo-banner-height, 0px));
}
}
diff --git a/client/src/components/users/UserActionsStep/UserActionsStep.jsx b/client/src/components/users/UserActionsStep/UserActionsStep.jsx
index 0c0fcf60..d3fb78d8 100755
--- a/client/src/components/users/UserActionsStep/UserActionsStep.jsx
+++ b/client/src/components/users/UserActionsStep/UserActionsStep.jsx
@@ -1,5 +1,5 @@
/*!
- * Copyright (c) 2024 PLANKA Software GmbH
+ * Copyright (c) 2026 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
@@ -104,6 +104,17 @@ const UserActionsStep = React.memo(({ onClose }) => {
context: 'title',
})}
+
+
+ {withAdministration
+ ? t('common.upgradeTeamToPro', { context: 'title' })
+ : t('common.discoverPlankaPro', { context: 'title' })}
+