Browse Source

feat(sponsors): drop the sidebar slot and simplify the Sponsors page

The sidebar no longer carries a sponsor card. The backend stops serving
the "sidebar" slot, so a sponsors.json entry that still lists it keeps
only its other slots and one listing nothing else is dropped. Its
rotation, three-sponsor cap and collapsed-rail logo rendering go with it.

The Sponsors page now lists only real sponsors: the "Your brand here"
card shows only as the empty state instead of sitting in the grid and
repeating the header's Become a sponsor button, and the Available
placements section and its slot-occupancy helper are removed. Cards are
larger, two per row on wide screens. Unused i18n keys are dropped from
every locale.
MHSanaei 11 hours ago
parent
commit
6f1dd0d757

+ 22 - 13
frontend/src/components/sponsor/SponsorCard.css

@@ -184,28 +184,37 @@
 .sponsor-card-card .sponsor-main {
 .sponsor-card-card .sponsor-main {
   flex-direction: column;
   flex-direction: column;
   align-items: flex-start;
   align-items: flex-start;
-  padding: 16px;
+  gap: 16px;
+  padding: 24px;
 }
 }
 
 
 .sponsor-card-card .sponsor-logo {
 .sponsor-card-card .sponsor-logo {
-  width: 56px;
-  height: 56px;
+  width: 72px;
+  height: 72px;
+  border-radius: 12px;
 }
 }
 
 
-.sponsor-card-card .sponsor-visit {
-  margin-top: auto;
+.sponsor-card-card .sponsor-logo-fallback {
+  font-size: 28px;
 }
 }
 
 
-.sponsor-card-icon {
-  justify-content: center;
-  padding: 6px;
-  border-color: transparent;
-  background: transparent;
+.sponsor-card-card .sponsor-body {
+  gap: 6px;
 }
 }
 
 
-.sponsor-card-icon .sponsor-logo {
-  width: 32px;
-  height: 32px;
+.sponsor-card-card .sponsor-title {
+  font-size: 17px;
+  line-height: 1.4;
+}
+
+.sponsor-card-card .sponsor-text {
+  font-size: 14px;
+  line-height: 1.6;
+}
+
+.sponsor-card-card .sponsor-visit {
+  margin-top: auto;
+  font-size: 14px;
 }
 }
 
 
 @media (max-width: 576px) {
 @media (max-width: 576px) {

+ 6 - 24
frontend/src/components/sponsor/SponsorCard.stories.tsx

@@ -7,7 +7,7 @@ import SponsorCard from './SponsorCard';
 const sponsor: Sponsor = {
 const sponsor: Sponsor = {
   id: 'acme-2026-10',
   id: 'acme-2026-10',
   name: 'Acme VPS',
   name: 'Acme VPS',
-  slots: ['dashboard', 'sidebar', 'page', 'login'],
+  slots: ['dashboard', 'page', 'login'],
   until: '2099-01-01T00:00:00Z',
   until: '2099-01-01T00:00:00Z',
   title: { en: 'Acme VPS — fast NVMe servers', fa: 'سرورهای سریع Acme' },
   title: { en: 'Acme VPS — fast NVMe servers', fa: 'سرورهای سریع Acme' },
   text: { en: 'Deploy 3X-UI in 60 seconds. 20% off for panel users.' },
   text: { en: 'Deploy 3X-UI in 60 seconds. 20% off for panel users.' },
@@ -57,9 +57,8 @@ const meta = {
   argTypes: {
   argTypes: {
     sponsor: { description: 'Sponsor entry from GET /sponsors.' },
     sponsor: { description: 'Sponsor entry from GET /sponsors.' },
     variant: {
     variant: {
-      description: 'banner (dashboard), compact (sidebar/login) or card (Sponsors page).',
+      description: 'banner (dashboard), compact (login) or card (Sponsors page).',
     },
     },
-    iconOnly: { description: 'Logo-only rendering for the collapsed sidebar rail.' },
     onClose: { description: 'When set, shows a close button (temporary dismiss).' },
     onClose: { description: 'When set, shows a close button (temporary dismiss).' },
   },
   },
   args: { sponsor },
   args: { sponsor },
@@ -81,7 +80,7 @@ export const Banner: Story = {
 export const Compact: Story = {
 export const Compact: Story = {
   args: { variant: 'compact', onClose: () => {} },
   args: { variant: 'compact', onClose: () => {} },
   render: (args) => (
   render: (args) => (
-    <div style={{ width: 204 }}>
+    <div style={{ width: 334 }}>
       <SponsorCard {...args} />
       <SponsorCard {...args} />
     </div>
     </div>
   ),
   ),
@@ -108,16 +107,16 @@ export const BannerLongCopy: Story = {
   },
   },
 };
 };
 
 
-export const SidebarLongCopy: Story = {
+export const LoginLongCopy: Story = {
   args: { variant: 'compact', sponsor: longSponsor, onClose: () => {} },
   args: { variant: 'compact', sponsor: longSponsor, onClose: () => {} },
   render: (args) => (
   render: (args) => (
-    <div style={{ width: 204 }}>
+    <div style={{ width: 334 }}>
       <SponsorCard {...args} />
       <SponsorCard {...args} />
     </div>
     </div>
   ),
   ),
   play: async ({ canvasElement }) => {
   play: async ({ canvasElement }) => {
     await expectNotClipped(canvasElement, '.sponsor-title');
     await expectNotClipped(canvasElement, '.sponsor-title');
-    await expectInsideCard(canvasElement, '.sponsor-text');
+    await expectNotClipped(canvasElement, '.sponsor-text');
     await expect(part(canvasElement, '.sponsor-main')).toHaveAttribute(
     await expect(part(canvasElement, '.sponsor-main')).toHaveAttribute(
       'title',
       'title',
       `${longSponsor.title.en}\n${longSponsor.text.en}`,
       `${longSponsor.title.en}\n${longSponsor.text.en}`,
@@ -125,19 +124,6 @@ export const SidebarLongCopy: Story = {
   },
   },
 };
 };
 
 
-export const LoginLongCopy: Story = {
-  args: { variant: 'compact', sponsor: longSponsor, onClose: () => {} },
-  render: (args) => (
-    <div style={{ width: 334 }}>
-      <SponsorCard {...args} />
-    </div>
-  ),
-  play: async ({ canvasElement }) => {
-    await expectNotClipped(canvasElement, '.sponsor-title');
-    await expectNotClipped(canvasElement, '.sponsor-text');
-  },
-};
-
 export const PageLongCopy: Story = {
 export const PageLongCopy: Story = {
   args: { variant: 'card', sponsor: longSponsor },
   args: { variant: 'card', sponsor: longSponsor },
   render: (args) => (
   render: (args) => (
@@ -151,7 +137,3 @@ export const PageLongCopy: Story = {
     await expectNotClipped(canvasElement, '.sponsor-title');
     await expectNotClipped(canvasElement, '.sponsor-title');
   },
   },
 };
 };
-
-export const IconOnly: Story = {
-  args: { iconOnly: true },
-};

+ 1 - 22
frontend/src/components/sponsor/SponsorCard.tsx

@@ -12,7 +12,6 @@ export type SponsorCardVariant = 'banner' | 'compact' | 'card';
 export interface SponsorCardProps {
 export interface SponsorCardProps {
   sponsor: Sponsor;
   sponsor: Sponsor;
   variant?: SponsorCardVariant;
   variant?: SponsorCardVariant;
-  iconOnly?: boolean;
   onClose?: () => void;
   onClose?: () => void;
 }
 }
 
 
@@ -36,33 +35,13 @@ function SponsorLogo({ sponsor }: { sponsor: Sponsor }) {
   );
   );
 }
 }
 
 
-export default function SponsorCard({
-  sponsor,
-  variant = 'banner',
-  iconOnly = false,
-  onClose,
-}: SponsorCardProps) {
+export default function SponsorCard({ sponsor, variant = 'banner', onClose }: SponsorCardProps) {
   const { t, i18n } = useTranslation();
   const { t, i18n } = useTranslation();
   const lang = i18n.resolvedLanguage || i18n.language || 'en';
   const lang = i18n.resolvedLanguage || i18n.language || 'en';
   const title = pickLocale(sponsor.title, lang) || sponsor.name;
   const title = pickLocale(sponsor.title, lang) || sponsor.name;
   const text = pickLocale(sponsor.text, lang);
   const text = pickLocale(sponsor.text, lang);
   const tag = t('pages.sponsors.tag');
   const tag = t('pages.sponsors.tag');
 
 
-  if (iconOnly) {
-    return (
-      <a
-        className="sponsor-card sponsor-card-icon"
-        href={sponsor.link}
-        target="_blank"
-        rel="noopener noreferrer sponsored"
-        title={`${tag} · ${title}`}
-        aria-label={`${tag}: ${title}`}
-      >
-        <SponsorLogo sponsor={sponsor} />
-      </a>
-    );
-  }
-
   return (
   return (
     <div className={`sponsor-card sponsor-card-${variant}`}>
     <div className={`sponsor-card sponsor-card-${variant}`}>
       <a
       <a

+ 3 - 21
frontend/src/components/sponsor/SponsorSlot.tsx

@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react';
+import { useState } from 'react';
 
 
 import { useSponsorsQuery } from '@/api/queries/useSponsorsQuery';
 import { useSponsorsQuery } from '@/api/queries/useSponsorsQuery';
 import {
 import {
@@ -9,47 +9,29 @@ import {
 } from '@/lib/sponsors';
 } from '@/lib/sponsors';
 import SponsorCard, { type SponsorCardVariant } from './SponsorCard';
 import SponsorCard, { type SponsorCardVariant } from './SponsorCard';
 
 
-const ROTATE_MS = 30_000;
-
 interface SponsorSlotProps {
 interface SponsorSlotProps {
   slot: Slot;
   slot: Slot;
   variant?: SponsorCardVariant;
   variant?: SponsorCardVariant;
-  iconOnly?: boolean;
-  rotate?: boolean;
   className?: string;
   className?: string;
 }
 }
 
 
-export default function SponsorSlot({
-  slot,
-  variant = 'banner',
-  iconOnly,
-  rotate,
-  className,
-}: SponsorSlotProps) {
+export default function SponsorSlot({ slot, variant = 'banner', className }: SponsorSlotProps) {
   const { data } = useSponsorsQuery();
   const { data } = useSponsorsQuery();
   const [, setDismissTick] = useState(0);
   const [, setDismissTick] = useState(0);
-  const [index, setIndex] = useState(0);
 
 
   // Re-filtered each render so a close (dismissTick bump) re-reads localStorage.
   // Re-filtered each render so a close (dismissTick bump) re-reads localStorage.
   const visible = sponsorsForSlot(data.sponsors, slot).filter(
   const visible = sponsorsForSlot(data.sponsors, slot).filter(
     (s) => !isSponsorDismissed(s.id, slot),
     (s) => !isSponsorDismissed(s.id, slot),
   );
   );
 
 
-  useEffect(() => {
-    if (!rotate || visible.length < 2) return;
-    const timer = window.setInterval(() => setIndex((i) => i + 1), ROTATE_MS);
-    return () => window.clearInterval(timer);
-  }, [rotate, visible.length]);
-
   if (visible.length === 0) return null;
   if (visible.length === 0) return null;
-  const sponsor = visible[(rotate ? index : 0) % visible.length];
+  const sponsor = visible[0];
 
 
   return (
   return (
     <div className={className}>
     <div className={className}>
       <SponsorCard
       <SponsorCard
         sponsor={sponsor}
         sponsor={sponsor}
         variant={variant}
         variant={variant}
-        iconOnly={iconOnly}
         onClose={() => {
         onClose={() => {
           dismissSponsor(sponsor.id, slot);
           dismissSponsor(sponsor.id, slot);
           setDismissTick((n) => n + 1);
           setDismissTick((n) => n + 1);

+ 0 - 4
frontend/src/layouts/AppSidebar.css

@@ -247,10 +247,6 @@
   padding: 8px 8px 12px;
   padding: 8px 8px 12px;
 }
 }
 
 
-.sider-sponsor {
-  margin-bottom: 6px;
-}
-
 .sidebar-pin {
 .sidebar-pin {
   display: inline-flex;
   display: inline-flex;
   align-items: center;
   align-items: center;

+ 0 - 9
frontend/src/layouts/AppSidebar.tsx

@@ -44,7 +44,6 @@ import { formatPanelVersion } from '@/lib/panel-version';
 import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme';
 import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme';
 import { useAllSettings } from '@/api/queries/useAllSettings';
 import { useAllSettings } from '@/api/queries/useAllSettings';
 import { useCommandPalette } from '@/components/command-palette/useCommandPalette';
 import { useCommandPalette } from '@/components/command-palette/useCommandPalette';
-import SponsorSlot from '@/components/sponsor/SponsorSlot';
 import './AppSidebar.css';
 import './AppSidebar.css';
 
 
 const DONATE_URL = 'https://donate.sanaei.dev/';
 const DONATE_URL = 'https://donate.sanaei.dev/';
@@ -452,13 +451,6 @@ export default function AppSidebar() {
           onClick={onMenuClick}
           onClick={onMenuClick}
         />
         />
         <div className="sider-footer">
         <div className="sider-footer">
-          <SponsorSlot
-            slot="sidebar"
-            variant="compact"
-            iconOnly={railCollapsed}
-            rotate
-            className="sider-sponsor"
-          />
           <VersionBadge version={panelVersion} collapsed={railCollapsed} />
           <VersionBadge version={panelVersion} collapsed={railCollapsed} />
         </div>
         </div>
       </Layout.Sider>
       </Layout.Sider>
@@ -544,7 +536,6 @@ export default function AppSidebar() {
           }}
           }}
         />
         />
         <div className="drawer-footer">
         <div className="drawer-footer">
-          <SponsorSlot slot="sidebar" variant="compact" rotate className="sider-sponsor" />
           <VersionBadge version={panelVersion} />
           <VersionBadge version={panelVersion} />
         </div>
         </div>
       </Drawer>
       </Drawer>

+ 1 - 23
frontend/src/lib/sponsors.ts

@@ -1,6 +1,6 @@
 import type { Sponsor } from '@/generated/types';
 import type { Sponsor } from '@/generated/types';
 
 
-export type SponsorSlot = 'dashboard' | 'sidebar' | 'page' | 'login';
+export type SponsorSlot = 'dashboard' | 'page' | 'login';
 
 
 const DISMISS_KEY = 'xui.sponsor.dismissed';
 const DISMISS_KEY = 'xui.sponsor.dismissed';
 export const SPONSOR_DISMISS_MS = 24 * 60 * 60 * 1000;
 export const SPONSOR_DISMISS_MS = 24 * 60 * 60 * 1000;
@@ -38,25 +38,3 @@ export function dismissSponsor(id: string, slot: SponsorSlot, now = Date.now())
     localStorage.setItem(DISMISS_KEY, JSON.stringify(next));
     localStorage.setItem(DISMISS_KEY, JSON.stringify(next));
   } catch {}
   } catch {}
 }
 }
-
-// Mirrors the backend: dashboard/login show one sponsor, the sidebar rotates up to three.
-export const SLOT_CAPACITY: Partial<Record<SponsorSlot, number>> = {
-  dashboard: 1,
-  login: 1,
-  sidebar: 3,
-};
-
-export interface PlacementStatus {
-  count: number;
-  capacity?: number;
-  takenUntil?: string;
-}
-
-// When full, a place frees up once enough bookings end to drop below capacity.
-export function placementStatus(sponsors: Sponsor[], slot: SponsorSlot): PlacementStatus {
-  const booked = sponsorsForSlot(sponsors, slot);
-  const capacity = SLOT_CAPACITY[slot];
-  if (!capacity || booked.length < capacity) return { count: booked.length, capacity };
-  const ends = booked.map((s) => s.until).sort((a, b) => Date.parse(a) - Date.parse(b));
-  return { count: booked.length, capacity, takenUntil: ends[booked.length - capacity] };
-}

+ 2 - 70
frontend/src/pages/sponsors/SponsorsPage.css

@@ -29,30 +29,14 @@
   color: #faad14;
   color: #faad14;
 }
 }
 
 
-.sponsors-page .sponsors-section-title {
-  margin: 32px 0 12px;
-  color: var(--ant-color-text-secondary);
-  font-weight: 600;
-  letter-spacing: 0.3px;
-}
-
 .sponsors-yourbrand {
 .sponsors-yourbrand {
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  align-items: flex-start;
+  align-items: center;
   gap: 8px;
   gap: 8px;
-  height: 100%;
-  min-height: 170px;
-  padding: 16px;
+  padding: 40px 24px;
   border: 1.5px dashed var(--ant-color-border);
   border: 1.5px dashed var(--ant-color-border);
   border-radius: var(--ant-border-radius-lg, 8px);
   border-radius: var(--ant-border-radius-lg, 8px);
-  background: transparent;
-}
-
-.sponsors-yourbrand.is-large {
-  align-items: center;
-  min-height: 0;
-  padding: 40px 24px;
   text-align: center;
   text-align: center;
   background: var(--bg-card, transparent);
   background: var(--bg-card, transparent);
 }
 }
@@ -82,58 +66,6 @@
   color: var(--ant-color-text-secondary);
   color: var(--ant-color-text-secondary);
 }
 }
 
 
-.sponsors-yourbrand:not(.is-large) .ant-btn {
-  margin-top: auto;
-}
-
-.sponsors-placement {
-  display: flex;
-  gap: 12px;
-  height: 100%;
-  padding: 14px 16px;
-  border: 1px solid var(--ant-color-border-secondary);
-  border-radius: var(--ant-border-radius-lg, 8px);
-  background: var(--bg-card, transparent);
-}
-
-.sponsors-placement-icon {
-  display: inline-flex;
-  flex: 0 0 auto;
-  align-items: center;
-  justify-content: center;
-  width: 36px;
-  height: 36px;
-  border-radius: 8px;
-  font-size: 17px;
-  color: var(--ant-color-primary);
-  background: var(--ant-color-primary-bg);
-}
-
-.sponsors-placement-title {
-  font-weight: 600;
-  color: var(--ant-color-text);
-}
-
-.sponsors-placement-body {
-  min-width: 0;
-}
-
-.sponsors-placement-status {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 4px;
-  margin-top: 8px;
-}
-
-.sponsors-placement-status .ant-tag {
-  margin: 0;
-}
-
-.sponsors-placement-desc {
-  font-size: 13px;
-  color: var(--ant-color-text-secondary);
-}
-
 @media (max-width: 768px) {
 @media (max-width: 768px) {
   .sponsors-page .content-area {
   .sponsors-page .content-area {
     padding: 12px;
     padding: 12px;

+ 7 - 90
frontend/src/pages/sponsors/SponsorsPage.tsx

@@ -1,22 +1,13 @@
 import { useMemo } from 'react';
 import { useMemo } from 'react';
-import type { ReactNode } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
-import { Button, Col, ConfigProvider, Layout, Row, Spin, Tag, Typography } from 'antd';
-import {
-  CrownOutlined,
-  DashboardOutlined,
-  LoginOutlined,
-  MenuUnfoldOutlined,
-  PlusOutlined,
-} from '@ant-design/icons';
+import { Button, Col, ConfigProvider, Layout, Row, Spin, Typography } from 'antd';
+import { CrownOutlined, PlusOutlined } from '@ant-design/icons';
 
 
 import { useTheme } from '@/hooks/useTheme';
 import { useTheme } from '@/hooks/useTheme';
 import AppSidebar from '@/layouts/AppSidebar';
 import AppSidebar from '@/layouts/AppSidebar';
 import { useSponsorsQuery } from '@/api/queries/useSponsorsQuery';
 import { useSponsorsQuery } from '@/api/queries/useSponsorsQuery';
 import SponsorCard from '@/components/sponsor/SponsorCard';
 import SponsorCard from '@/components/sponsor/SponsorCard';
-import { IntlUtil } from '@/utils';
-import { useDatepicker } from '@/hooks/useDatepicker';
-import { placementStatus, sponsorsForSlot, type SponsorSlot } from '@/lib/sponsors';
+import { sponsorsForSlot } from '@/lib/sponsors';
 import './SponsorsPage.css';
 import './SponsorsPage.css';
 
 
 function BecomeButton({ contact, block }: { contact?: string; block?: boolean }) {
 function BecomeButton({ contact, block }: { contact?: string; block?: boolean }) {
@@ -36,10 +27,10 @@ function BecomeButton({ contact, block }: { contact?: string; block?: boolean })
   );
   );
 }
 }
 
 
-function YourBrandCard({ contact, large }: { contact?: string; large?: boolean }) {
+function YourBrandCard({ contact }: { contact?: string }) {
   const { t } = useTranslation();
   const { t } = useTranslation();
   return (
   return (
-    <div className={`sponsors-yourbrand${large ? ' is-large' : ''}`}>
+    <div className="sponsors-yourbrand">
       <span className="sponsors-yourbrand-icon">
       <span className="sponsors-yourbrand-icon">
         <PlusOutlined />
         <PlusOutlined />
       </span>
       </span>
@@ -56,34 +47,6 @@ export default function SponsorsPage() {
   const { data, fetched } = useSponsorsQuery();
   const { data, fetched } = useSponsorsQuery();
   const sponsors = sponsorsForSlot(data.sponsors, 'page');
   const sponsors = sponsorsForSlot(data.sponsors, 'page');
 
 
-  const { datepicker } = useDatepicker();
-  const placements: { slot: SponsorSlot; icon: ReactNode; title: string; desc: string }[] = [
-    {
-      slot: 'dashboard',
-      icon: <DashboardOutlined />,
-      title: t('pages.sponsors.placementDashboard'),
-      desc: t('pages.sponsors.placementDashboardDesc'),
-    },
-    {
-      slot: 'sidebar',
-      icon: <MenuUnfoldOutlined />,
-      title: t('pages.sponsors.placementSidebar'),
-      desc: t('pages.sponsors.placementSidebarDesc'),
-    },
-    {
-      slot: 'login',
-      icon: <LoginOutlined />,
-      title: t('pages.sponsors.placementLogin'),
-      desc: t('pages.sponsors.placementLoginDesc'),
-    },
-    {
-      slot: 'page',
-      icon: <CrownOutlined />,
-      title: t('pages.sponsors.placementPage'),
-      desc: t('pages.sponsors.placementPageDesc'),
-    },
-  ];
-
   const pageClass = useMemo(() => {
   const pageClass = useMemo(() => {
     const classes = ['sponsors-page'];
     const classes = ['sponsors-page'];
     if (isDark) classes.push('is-dark');
     if (isDark) classes.push('is-dark');
@@ -110,63 +73,17 @@ export default function SponsorsPage() {
 
 
               <Spin spinning={!fetched} delay={200}>
               <Spin spinning={!fetched} delay={200}>
                 {sponsors.length === 0 ? (
                 {sponsors.length === 0 ? (
-                  fetched && <YourBrandCard contact={data.contact} large />
+                  fetched && <YourBrandCard contact={data.contact} />
                 ) : (
                 ) : (
                   <Row gutter={[16, 16]}>
                   <Row gutter={[16, 16]}>
                     {sponsors.map((sponsor) => (
                     {sponsors.map((sponsor) => (
-                      <Col key={sponsor.id} xs={24} sm={12} xl={8}>
+                      <Col key={sponsor.id} xs={24} lg={12}>
                         <SponsorCard sponsor={sponsor} variant="card" />
                         <SponsorCard sponsor={sponsor} variant="card" />
                       </Col>
                       </Col>
                     ))}
                     ))}
-                    {data.contact && (
-                      <Col xs={24} sm={12} xl={8}>
-                        <YourBrandCard contact={data.contact} />
-                      </Col>
-                    )}
                   </Row>
                   </Row>
                 )}
                 )}
               </Spin>
               </Spin>
-
-              <Typography.Title level={5} className="sponsors-section-title">
-                {t('pages.sponsors.placements')}
-              </Typography.Title>
-              <Row gutter={[16, 16]}>
-                {placements.map((p) => {
-                  const status = placementStatus(data.sponsors, p.slot);
-                  return (
-                    <Col key={p.slot} xs={24} sm={12} xl={6}>
-                      <div className="sponsors-placement">
-                        <span className="sponsors-placement-icon">{p.icon}</span>
-                        <div className="sponsors-placement-body">
-                          <div className="sponsors-placement-title">{p.title}</div>
-                          <div className="sponsors-placement-desc">{p.desc}</div>
-                          <div className="sponsors-placement-status">
-                            {status.takenUntil ? (
-                              <Tag color="orange">
-                                {t('pages.sponsors.takenUntil', {
-                                  date: IntlUtil.formatDate(status.takenUntil, datepicker),
-                                })}
-                              </Tag>
-                            ) : (
-                              <Tag color="green">{t('pages.sponsors.available')}</Tag>
-                            )}
-                            {status.count > 0 && !status.takenUntil && (
-                              <Tag>
-                                {t('pages.sponsors.activeCount', {
-                                  count:
-                                    status.capacity && status.capacity > 1
-                                      ? `${status.count}/${status.capacity}`
-                                      : status.count,
-                                })}
-                              </Tag>
-                            )}
-                          </div>
-                        </div>
-                      </div>
-                    </Col>
-                  );
-                })}
-              </Row>
             </div>
             </div>
           </Layout.Content>
           </Layout.Content>
         </Layout>
         </Layout>

+ 5 - 56
frontend/src/test/sponsors.test.tsx

@@ -1,12 +1,6 @@
 import { afterEach, describe, expect, test } from 'vitest';
 import { afterEach, describe, expect, test } from 'vitest';
 
 
-import {
-  SPONSOR_DISMISS_MS,
-  dismissSponsor,
-  isSponsorDismissed,
-  pickLocale,
-  placementStatus,
-} from '@/lib/sponsors';
+import { SPONSOR_DISMISS_MS, dismissSponsor, isSponsorDismissed, pickLocale } from '@/lib/sponsors';
 
 
 afterEach(() => {
 afterEach(() => {
   localStorage.clear();
   localStorage.clear();
@@ -33,10 +27,10 @@ describe('sponsor dismissal', () => {
   const t0 = 1_800_000_000_000;
   const t0 = 1_800_000_000_000;
 
 
   test('hides only the dismissed slot until the window elapses', () => {
   test('hides only the dismissed slot until the window elapses', () => {
-    dismissSponsor('acme', 'sidebar', t0);
-    expect(isSponsorDismissed('acme', 'sidebar', t0 + SPONSOR_DISMISS_MS - 1)).toBe(true);
+    dismissSponsor('acme', 'login', t0);
+    expect(isSponsorDismissed('acme', 'login', t0 + SPONSOR_DISMISS_MS - 1)).toBe(true);
     expect(isSponsorDismissed('acme', 'dashboard', t0)).toBe(false);
     expect(isSponsorDismissed('acme', 'dashboard', t0)).toBe(false);
-    expect(isSponsorDismissed('acme', 'sidebar', t0 + SPONSOR_DISMISS_MS)).toBe(false);
+    expect(isSponsorDismissed('acme', 'login', t0 + SPONSOR_DISMISS_MS)).toBe(false);
   });
   });
 
 
   test('prunes expired entries on the next dismiss', () => {
   test('prunes expired entries on the next dismiss', () => {
@@ -48,51 +42,6 @@ describe('sponsor dismissal', () => {
 
 
   test('corrupt storage is treated as nothing dismissed', () => {
   test('corrupt storage is treated as nothing dismissed', () => {
     localStorage.setItem('xui.sponsor.dismissed', 'not json');
     localStorage.setItem('xui.sponsor.dismissed', 'not json');
-    expect(isSponsorDismissed('acme', 'sidebar', t0)).toBe(false);
-  });
-});
-
-describe('placementStatus', () => {
-  const sp = (id: string, slots: string[], until: string) => ({
-    id,
-    name: id,
-    slots,
-    until,
-    title: {},
-    text: {},
-    link: 'https://x.example/',
-  });
-
-  test('exclusive slot is taken until the latest booking ends', () => {
-    const sponsors = [
-      sp('a', ['dashboard'], '2026-11-01T00:00:00Z'),
-      sp('b', ['dashboard'], '2026-12-15T00:00:00Z'),
-    ];
-    expect(placementStatus(sponsors, 'dashboard')).toEqual({
-      count: 2,
-      capacity: 1,
-      takenUntil: '2026-12-15T00:00:00Z',
-    });
-  });
-
-  test('sidebar is full at three and frees up when the earliest ends', () => {
-    const sponsors = [
-      sp('a', ['sidebar'], '2026-12-01T00:00:00Z'),
-      sp('b', ['sidebar'], '2026-10-20T00:00:00Z'),
-      sp('c', ['sidebar'], '2026-11-10T00:00:00Z'),
-    ];
-    expect(placementStatus(sponsors, 'sidebar')).toEqual({
-      count: 3,
-      capacity: 3,
-      takenUntil: '2026-10-20T00:00:00Z',
-    });
-    expect(placementStatus(sponsors.slice(0, 2), 'sidebar')).toEqual({ count: 2, capacity: 3 });
-  });
-
-  test('uncapped page slot reports only a count', () => {
-    expect(placementStatus([sp('a', ['page'], '2026-12-01T00:00:00Z')], 'page')).toEqual({
-      count: 1,
-      capacity: undefined,
-    });
+    expect(isSponsorDismissed('acme', 'login', t0)).toBe(false);
   });
   });
 });
 });

+ 3 - 10
internal/web/service/panel/sponsor.go

@@ -45,7 +45,6 @@ const (
 	sponsorsErrTTL   = 10 * time.Minute
 	sponsorsErrTTL   = 10 * time.Minute
 	maxSponsorsBytes = 256 << 10
 	maxSponsorsBytes = 256 << 10
 	maxLogoBytes     = 256 << 10
 	maxLogoBytes     = 256 << 10
-	maxSidebarSlots  = 3
 	localSponsorsDir = "../sponsors/3X"
 	localSponsorsDir = "../sponsors/3X"
 	sponsorLogoPath  = "/sponsors/logo/"
 	sponsorLogoPath  = "/sponsors/logo/"
 )
 )
@@ -67,7 +66,7 @@ var (
 
 
 	// The panel proxies logos from sponsorLogoBase: CSP stays 'self' and no third party sees admin IPs.
 	// The panel proxies logos from sponsorLogoBase: CSP stays 'self' and no third party sees admin IPs.
 	sponsorLogoRe = regexp.MustCompile(`^[A-Za-z0-9_-][A-Za-z0-9._-]*\.(png|webp|jpg)$`)
 	sponsorLogoRe = regexp.MustCompile(`^[A-Za-z0-9_-][A-Za-z0-9._-]*\.(png|webp|jpg)$`)
-	sponsorSlots  = map[string]bool{"dashboard": true, "sidebar": true, "page": true, "login": true}
+	sponsorSlots  = map[string]bool{"dashboard": true, "page": true, "login": true}
 
 
 	sponsorsMu      sync.Mutex
 	sponsorsMu      sync.Mutex
 	sponsorsRaw     *SponsorList
 	sponsorsRaw     *SponsorList
@@ -217,7 +216,6 @@ func activeSponsors(raw *SponsorList, now time.Time) *SponsorList {
 	if strings.HasPrefix(raw.Contact, "https://") {
 	if strings.HasPrefix(raw.Contact, "https://") {
 		out.Contact = raw.Contact
 		out.Contact = raw.Contact
 	}
 	}
-	sidebarTaken := 0
 	for _, sp := range raw.Sponsors {
 	for _, sp := range raw.Sponsors {
 		// A missing enable counts as on, so a forgotten field never hides a paid slot.
 		// A missing enable counts as on, so a forgotten field never hides a paid slot.
 		disabled := sp.Enable != nil && !*sp.Enable
 		disabled := sp.Enable != nil && !*sp.Enable
@@ -233,18 +231,13 @@ func activeSponsors(raw *SponsorList, now time.Time) *SponsorList {
 		}
 		}
 		slots := make([]string, 0, len(sp.Slots))
 		slots := make([]string, 0, len(sp.Slots))
 		for _, slot := range sp.Slots {
 		for _, slot := range sp.Slots {
-			// The sidebar rotates, so capping it keeps each paid card on screen long enough.
-			if !sponsorSlots[slot] || (slot == "sidebar" && sidebarTaken >= maxSidebarSlots) {
-				continue
+			if sponsorSlots[slot] {
+				slots = append(slots, slot)
 			}
 			}
-			slots = append(slots, slot)
 		}
 		}
 		if len(slots) == 0 {
 		if len(slots) == 0 {
 			continue
 			continue
 		}
 		}
-		if slices.Contains(slots, "sidebar") {
-			sidebarTaken++
-		}
 		sp.Slots = slots
 		sp.Slots = slots
 		out.Sponsors = append(out.Sponsors, sp)
 		out.Sponsors = append(out.Sponsors, sp)
 	}
 	}

+ 2 - 31
internal/web/service/panel/sponsor_test.go

@@ -6,7 +6,6 @@ import (
 	"net/http/httptest"
 	"net/http/httptest"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
-	"slices"
 	"strings"
 	"strings"
 	"sync/atomic"
 	"sync/atomic"
 	"testing"
 	"testing"
@@ -22,7 +21,7 @@ func validSponsor() Sponsor {
 	return Sponsor{
 	return Sponsor{
 		ID:    "acme",
 		ID:    "acme",
 		Name:  "Acme",
 		Name:  "Acme",
-		Slots: []string{"dashboard", "sidebar"},
+		Slots: []string{"dashboard"},
 		Until: sponsorTestNow.Add(24 * time.Hour),
 		Until: sponsorTestNow.Add(24 * time.Hour),
 		Logo:  "acme.png",
 		Logo:  "acme.png",
 		Link:  "https://acme.example/",
 		Link:  "https://acme.example/",
@@ -59,34 +58,6 @@ func TestActiveSponsorsFilters(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestActiveSponsorsCapsSidebarAtThree(t *testing.T) {
-	raw := &SponsorList{}
-	for _, id := range []string{"expired", "a", "b", "c", "d", "e"} {
-		sp := validSponsor()
-		sp.ID = id
-		sp.Slots = []string{"sidebar", "page"}
-		if id == "expired" {
-			sp.Until = sponsorTestNow
-		}
-		if id == "e" {
-			sp.Slots = []string{"sidebar"}
-		}
-		raw.Sponsors = append(raw.Sponsors, sp)
-	}
-	got := activeSponsors(raw, sponsorTestNow)
-	want := map[string][]string{
-		"a": {"sidebar", "page"}, "b": {"sidebar", "page"}, "c": {"sidebar", "page"}, "d": {"page"},
-	}
-	if len(got.Sponsors) != len(want) {
-		t.Fatalf("got %d sponsors, want %d (e has only sidebar and must drop)", len(got.Sponsors), len(want))
-	}
-	for _, sp := range got.Sponsors {
-		if !slices.Equal(sp.Slots, want[sp.ID]) {
-			t.Errorf("%s slots = %v, want %v", sp.ID, sp.Slots, want[sp.ID])
-		}
-	}
-}
-
 func TestActiveSponsorsLogoName(t *testing.T) {
 func TestActiveSponsorsLogoName(t *testing.T) {
 	cases := []struct{ logo, want string }{
 	cases := []struct{ logo, want string }{
 		{"acme.png", "/sponsors/logo/acme.png"},
 		{"acme.png", "/sponsors/logo/acme.png"},
@@ -114,7 +85,7 @@ func TestActiveSponsorsLogoName(t *testing.T) {
 
 
 func TestActiveSponsorsResolvesLogoAndSlots(t *testing.T) {
 func TestActiveSponsorsResolvesLogoAndSlots(t *testing.T) {
 	sp := validSponsor()
 	sp := validSponsor()
-	sp.Slots = []string{"subpage", "login"}
+	sp.Slots = []string{"subpage", "sidebar", "login"}
 	got := activeSponsors(&SponsorList{Contact: "javascript:x", Sponsors: []Sponsor{sp}}, sponsorTestNow)
 	got := activeSponsors(&SponsorList{Contact: "javascript:x", Sponsors: []Sponsor{sp}}, sponsorTestNow)
 	if len(got.Sponsors) != 1 {
 	if len(got.Sponsors) != 1 {
 		t.Fatalf("got %d sponsors, want 1", len(got.Sponsors))
 		t.Fatalf("got %d sponsors, want 1", len(got.Sponsors))

+ 1 - 13
internal/web/translation/ar-EG.json

@@ -2423,19 +2423,7 @@
       "tag": "راعٍ",
       "tag": "راعٍ",
       "visit": "زيارة",
       "visit": "زيارة",
       "yourBrand": "علامتك التجارية هنا",
       "yourBrand": "علامتك التجارية هنا",
-      "yourBrandText": "ضع شركتك أمام آلاف مسؤولي الخوادم الذين يفتحون 3X-UI كل يوم.",
-      "placements": "المواضع المتاحة",
-      "placementDashboard": "لافتة لوحة المعلومات",
-      "placementDashboardDesc": "موضع مميز أعلى لوحة المعلومات، راعٍ واحد في كل مرة.",
-      "placementSidebar": "بطاقة الشريط الجانبي",
-      "placementSidebarDesc": "تظهر في كل صفحات اللوحة وتتناوب بين الرعاة.",
-      "placementLogin": "صفحة تسجيل الدخول",
-      "placementLoginDesc": "يراها المسؤول عند كل تسجيل دخول.",
-      "placementPage": "صفحة الرعاة",
-      "placementPageDesc": "مدرجة هنا مع الشعار والوصف والرابط.",
-      "available": "متاح",
-      "takenUntil": "محجوز حتى {date}",
-      "activeCount": "{count} نشط"
+      "yourBrandText": "ضع شركتك أمام آلاف مسؤولي الخوادم الذين يفتحون 3X-UI كل يوم."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/en-US.json

@@ -2423,19 +2423,7 @@
       "tag": "Sponsor",
       "tag": "Sponsor",
       "visit": "Visit",
       "visit": "Visit",
       "yourBrand": "Your brand here",
       "yourBrand": "Your brand here",
-      "yourBrandText": "Put your company in front of thousands of server admins who open 3X-UI every day.",
-      "placements": "Available placements",
-      "placementDashboard": "Overview banner",
-      "placementDashboardDesc": "Premium spot at the top of the dashboard, one sponsor at a time.",
-      "placementSidebar": "Sidebar card",
-      "placementSidebarDesc": "Shown on every panel page, rotating between sponsors.",
-      "placementLogin": "Login page",
-      "placementLoginDesc": "Seen by the admin at every sign-in.",
-      "placementPage": "Sponsors page",
-      "placementPageDesc": "Listed here with logo, description and link.",
-      "available": "Available",
-      "takenUntil": "Taken until {date}",
-      "activeCount": "{count} active"
+      "yourBrandText": "Put your company in front of thousands of server admins who open 3X-UI every day."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/es-ES.json

@@ -2423,19 +2423,7 @@
       "tag": "Patrocinador",
       "tag": "Patrocinador",
       "visit": "Visitar",
       "visit": "Visitar",
       "yourBrand": "Tu marca aquí",
       "yourBrand": "Tu marca aquí",
-      "yourBrandText": "Muestra tu empresa a miles de administradores de servidores que abren 3X-UI cada día.",
-      "placements": "Espacios disponibles",
-      "placementDashboard": "Banner del panel",
-      "placementDashboardDesc": "Espacio premium en la parte superior del panel, un patrocinador a la vez.",
-      "placementSidebar": "Tarjeta lateral",
-      "placementSidebarDesc": "Se muestra en todas las páginas del panel, rotando entre patrocinadores.",
-      "placementLogin": "Página de inicio de sesión",
-      "placementLoginDesc": "La ve el administrador en cada inicio de sesión.",
-      "placementPage": "Página de patrocinadores",
-      "placementPageDesc": "Aparece aquí con logo, descripción y enlace.",
-      "available": "Disponible",
-      "takenUntil": "Ocupado hasta {date}",
-      "activeCount": "{count} activos"
+      "yourBrandText": "Muestra tu empresa a miles de administradores de servidores que abren 3X-UI cada día."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/fa-IR.json

@@ -2423,19 +2423,7 @@
       "tag": "اسپانسر",
       "tag": "اسپانسر",
       "visit": "مشاهده",
       "visit": "مشاهده",
       "yourBrand": "جای برند شما",
       "yourBrand": "جای برند شما",
-      "yourBrandText": "شرکت خود را جلوی چشم هزاران ادمین سروری بگذارید که هر روز 3X-UI را باز می‌کنند.",
-      "placements": "جایگاه‌های موجود",
-      "placementDashboard": "بنر داشبورد",
-      "placementDashboardDesc": "جایگاه ویژه بالای صفحه‌ی داشبورد، هر بار فقط یک اسپانسر.",
-      "placementSidebar": "کارت سایدبار",
-      "placementSidebarDesc": "در همه‌ی صفحات پنل نمایش داده می‌شود و بین اسپانسرها چرخش دارد.",
-      "placementLogin": "صفحه‌ی ورود",
-      "placementLoginDesc": "ادمین در هر بار ورود آن را می‌بیند.",
-      "placementPage": "صفحه‌ی اسپانسرها",
-      "placementPageDesc": "همراه با لوگو، توضیحات و لینک در همین صفحه.",
-      "available": "خالی",
-      "takenUntil": "پر شده تا {date}",
-      "activeCount": "{count} فعال"
+      "yourBrandText": "شرکت خود را جلوی چشم هزاران ادمین سروری بگذارید که هر روز 3X-UI را باز می‌کنند."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/id-ID.json

@@ -2423,19 +2423,7 @@
       "tag": "Sponsor",
       "tag": "Sponsor",
       "visit": "Kunjungi",
       "visit": "Kunjungi",
       "yourBrand": "Merek Anda di sini",
       "yourBrand": "Merek Anda di sini",
-      "yourBrandText": "Tampilkan perusahaan Anda kepada ribuan admin server yang membuka 3X-UI setiap hari.",
-      "placements": "Slot yang tersedia",
-      "placementDashboard": "Banner dasbor",
-      "placementDashboardDesc": "Tempat premium di atas dasbor, satu sponsor dalam satu waktu.",
-      "placementSidebar": "Kartu sidebar",
-      "placementSidebarDesc": "Tampil di setiap halaman panel, bergantian antar sponsor.",
-      "placementLogin": "Halaman login",
-      "placementLoginDesc": "Dilihat admin setiap kali masuk.",
-      "placementPage": "Halaman sponsor",
-      "placementPageDesc": "Tercantum di sini dengan logo, deskripsi, dan tautan.",
-      "available": "Tersedia",
-      "takenUntil": "Terisi hingga {date}",
-      "activeCount": "{count} aktif"
+      "yourBrandText": "Tampilkan perusahaan Anda kepada ribuan admin server yang membuka 3X-UI setiap hari."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/ja-JP.json

@@ -2423,19 +2423,7 @@
       "tag": "スポンサー",
       "tag": "スポンサー",
       "visit": "訪問",
       "visit": "訪問",
       "yourBrand": "ここにあなたのブランドを",
       "yourBrand": "ここにあなたのブランドを",
-      "yourBrandText": "毎日 3X-UI を開く何千人ものサーバー管理者に御社をアピールできます。",
-      "placements": "掲載枠",
-      "placementDashboard": "ダッシュボードバナー",
-      "placementDashboardDesc": "ダッシュボード上部のプレミアム枠。一度に 1 社のみ。",
-      "placementSidebar": "サイドバーカード",
-      "placementSidebarDesc": "パネルの全ページに表示され、スポンサー間でローテーションします。",
-      "placementLogin": "ログインページ",
-      "placementLoginDesc": "管理者がログインするたびに表示されます。",
-      "placementPage": "スポンサーページ",
-      "placementPageDesc": "ロゴ、説明、リンク付きでここに掲載されます。",
-      "available": "空きあり",
-      "takenUntil": "{date} まで予約済み",
-      "activeCount": "{count} 件掲載中"
+      "yourBrandText": "毎日 3X-UI を開く何千人ものサーバー管理者に御社をアピールできます。"
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/pt-BR.json

@@ -2423,19 +2423,7 @@
       "tag": "Patrocinador",
       "tag": "Patrocinador",
       "visit": "Visitar",
       "visit": "Visitar",
       "yourBrand": "Sua marca aqui",
       "yourBrand": "Sua marca aqui",
-      "yourBrandText": "Coloque sua empresa diante de milhares de administradores de servidores que abrem o 3X-UI todos os dias.",
-      "placements": "Espaços disponíveis",
-      "placementDashboard": "Banner do painel",
-      "placementDashboardDesc": "Espaço premium no topo do painel, um patrocinador por vez.",
-      "placementSidebar": "Cartão lateral",
-      "placementSidebarDesc": "Exibido em todas as páginas do painel, alternando entre patrocinadores.",
-      "placementLogin": "Página de login",
-      "placementLoginDesc": "Visto pelo administrador a cada login.",
-      "placementPage": "Página de patrocinadores",
-      "placementPageDesc": "Listado aqui com logo, descrição e link.",
-      "available": "Disponível",
-      "takenUntil": "Ocupado até {date}",
-      "activeCount": "{count} ativos"
+      "yourBrandText": "Coloque sua empresa diante de milhares de administradores de servidores que abrem o 3X-UI todos os dias."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/ru-RU.json

@@ -2423,19 +2423,7 @@
       "tag": "Спонсор",
       "tag": "Спонсор",
       "visit": "Перейти",
       "visit": "Перейти",
       "yourBrand": "Здесь может быть ваш бренд",
       "yourBrand": "Здесь может быть ваш бренд",
-      "yourBrandText": "Покажите свою компанию тысячам администраторов серверов, которые каждый день открывают 3X-UI.",
-      "placements": "Доступные места",
-      "placementDashboard": "Баннер на главной",
-      "placementDashboardDesc": "Премиум-место вверху панели, один спонсор за раз.",
-      "placementSidebar": "Карточка в боковой панели",
-      "placementSidebarDesc": "Показывается на всех страницах панели, спонсоры чередуются.",
-      "placementLogin": "Страница входа",
-      "placementLoginDesc": "Администратор видит её при каждом входе.",
-      "placementPage": "Страница спонсоров",
-      "placementPageDesc": "Размещение здесь с логотипом, описанием и ссылкой.",
-      "available": "Свободно",
-      "takenUntil": "Занято до {date}",
-      "activeCount": "Активных: {count}"
+      "yourBrandText": "Покажите свою компанию тысячам администраторов серверов, которые каждый день открывают 3X-UI."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/tr-TR.json

@@ -2423,19 +2423,7 @@
       "tag": "Sponsor",
       "tag": "Sponsor",
       "visit": "Ziyaret et",
       "visit": "Ziyaret et",
       "yourBrand": "Markanız burada",
       "yourBrand": "Markanız burada",
-      "yourBrandText": "Şirketinizi her gün 3X-UI açan binlerce sunucu yöneticisinin önüne çıkarın.",
-      "placements": "Mevcut yerleşimler",
-      "placementDashboard": "Genel bakış afişi",
-      "placementDashboardDesc": "Panonun üstünde premium alan, aynı anda tek sponsor.",
-      "placementSidebar": "Kenar çubuğu kartı",
-      "placementSidebarDesc": "Tüm panel sayfalarında gösterilir, sponsorlar arasında döner.",
-      "placementLogin": "Giriş sayfası",
-      "placementLoginDesc": "Yönetici her girişte görür.",
-      "placementPage": "Sponsorlar sayfası",
-      "placementPageDesc": "Logo, açıklama ve bağlantıyla burada listelenir.",
-      "available": "Müsait",
-      "takenUntil": "{date} tarihine kadar dolu",
-      "activeCount": "{count} aktif"
+      "yourBrandText": "Şirketinizi her gün 3X-UI açan binlerce sunucu yöneticisinin önüne çıkarın."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/uk-UA.json

@@ -2423,19 +2423,7 @@
       "tag": "Спонсор",
       "tag": "Спонсор",
       "visit": "Перейти",
       "visit": "Перейти",
       "yourBrand": "Тут може бути ваш бренд",
       "yourBrand": "Тут може бути ваш бренд",
-      "yourBrandText": "Покажіть свою компанію тисячам адміністраторів серверів, які щодня відкривають 3X-UI.",
-      "placements": "Доступні місця",
-      "placementDashboard": "Банер на головній",
-      "placementDashboardDesc": "Преміум-місце вгорі панелі, один спонсор за раз.",
-      "placementSidebar": "Картка в бічній панелі",
-      "placementSidebarDesc": "Показується на всіх сторінках панелі, спонсори чергуються.",
-      "placementLogin": "Сторінка входу",
-      "placementLoginDesc": "Адміністратор бачить її під час кожного входу.",
-      "placementPage": "Сторінка спонсорів",
-      "placementPageDesc": "Розміщення тут із логотипом, описом і посиланням.",
-      "available": "Вільно",
-      "takenUntil": "Зайнято до {date}",
-      "activeCount": "Активних: {count}"
+      "yourBrandText": "Покажіть свою компанію тисячам адміністраторів серверів, які щодня відкривають 3X-UI."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/vi-VN.json

@@ -2423,19 +2423,7 @@
       "tag": "Tài trợ",
       "tag": "Tài trợ",
       "visit": "Truy cập",
       "visit": "Truy cập",
       "yourBrand": "Thương hiệu của bạn ở đây",
       "yourBrand": "Thương hiệu của bạn ở đây",
-      "yourBrandText": "Đưa công ty bạn đến hàng nghìn quản trị viên máy chủ mở 3X-UI mỗi ngày.",
-      "placements": "Vị trí hiện có",
-      "placementDashboard": "Banner tổng quan",
-      "placementDashboardDesc": "Vị trí cao cấp ở đầu bảng điều khiển, mỗi lần một nhà tài trợ.",
-      "placementSidebar": "Thẻ thanh bên",
-      "placementSidebarDesc": "Hiển thị trên mọi trang của panel, luân phiên giữa các nhà tài trợ.",
-      "placementLogin": "Trang đăng nhập",
-      "placementLoginDesc": "Quản trị viên thấy mỗi lần đăng nhập.",
-      "placementPage": "Trang nhà tài trợ",
-      "placementPageDesc": "Được liệt kê tại đây với logo, mô tả và liên kết.",
-      "available": "Còn trống",
-      "takenUntil": "Đã đặt đến {date}",
-      "activeCount": "{count} đang hoạt động"
+      "yourBrandText": "Đưa công ty bạn đến hàng nghìn quản trị viên máy chủ mở 3X-UI mỗi ngày."
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/zh-CN.json

@@ -2423,19 +2423,7 @@
       "tag": "赞助",
       "tag": "赞助",
       "visit": "访问",
       "visit": "访问",
       "yourBrand": "您的品牌展示位",
       "yourBrand": "您的品牌展示位",
-      "yourBrandText": "让每天打开 3X-UI 的数千名服务器管理员看到您的公司。",
-      "placements": "可用展示位",
-      "placementDashboard": "概览横幅",
-      "placementDashboardDesc": "仪表盘顶部的高级位置,每次仅一家赞助商。",
-      "placementSidebar": "侧边栏卡片",
-      "placementSidebarDesc": "在面板所有页面显示,并在赞助商之间轮换。",
-      "placementLogin": "登录页",
-      "placementLoginDesc": "管理员每次登录时都能看到。",
-      "placementPage": "赞助商页面",
-      "placementPageDesc": "在此页面展示徽标、简介和链接。",
-      "available": "可预订",
-      "takenUntil": "已预订至 {date}",
-      "activeCount": "{count} 个进行中"
+      "yourBrandText": "让每天打开 3X-UI 的数千名服务器管理员看到您的公司。"
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {

+ 1 - 13
internal/web/translation/zh-TW.json

@@ -2423,19 +2423,7 @@
       "tag": "贊助",
       "tag": "贊助",
       "visit": "造訪",
       "visit": "造訪",
       "yourBrand": "您的品牌展示位",
       "yourBrand": "您的品牌展示位",
-      "yourBrandText": "讓每天打開 3X-UI 的數千名伺服器管理員看到您的公司。",
-      "placements": "可用展示位",
-      "placementDashboard": "總覽橫幅",
-      "placementDashboardDesc": "儀表板頂部的高級位置,每次僅一家贊助商。",
-      "placementSidebar": "側邊欄卡片",
-      "placementSidebarDesc": "在面板所有頁面顯示,並在贊助商之間輪換。",
-      "placementLogin": "登入頁",
-      "placementLoginDesc": "管理員每次登入時都能看到。",
-      "placementPage": "贊助商頁面",
-      "placementPageDesc": "在此頁面展示標誌、簡介與連結。",
-      "available": "可預訂",
-      "takenUntil": "已預訂至 {date}",
-      "activeCount": "{count} 個進行中"
+      "yourBrandText": "讓每天打開 3X-UI 的數千名伺服器管理員看到您的公司。"
     }
     }
   },
   },
   "tgbot": {
   "tgbot": {