diff --git a/frontend/src/components/sponsor/SponsorCard.css b/frontend/src/components/sponsor/SponsorCard.css index caa9fbfe7..0b3318257 100644 --- a/frontend/src/components/sponsor/SponsorCard.css +++ b/frontend/src/components/sponsor/SponsorCard.css @@ -184,28 +184,37 @@ .sponsor-card-card .sponsor-main { flex-direction: column; align-items: flex-start; - padding: 16px; + gap: 16px; + padding: 24px; } .sponsor-card-card .sponsor-logo { - width: 56px; - height: 56px; + width: 72px; + height: 72px; + border-radius: 12px; +} + +.sponsor-card-card .sponsor-logo-fallback { + font-size: 28px; +} + +.sponsor-card-card .sponsor-body { + gap: 6px; +} + +.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; -} - -.sponsor-card-icon { - justify-content: center; - padding: 6px; - border-color: transparent; - background: transparent; -} - -.sponsor-card-icon .sponsor-logo { - width: 32px; - height: 32px; + font-size: 14px; } @media (max-width: 576px) { diff --git a/frontend/src/components/sponsor/SponsorCard.stories.tsx b/frontend/src/components/sponsor/SponsorCard.stories.tsx index b02f2ff2c..da8b85242 100644 --- a/frontend/src/components/sponsor/SponsorCard.stories.tsx +++ b/frontend/src/components/sponsor/SponsorCard.stories.tsx @@ -7,7 +7,7 @@ import SponsorCard from './SponsorCard'; const sponsor: Sponsor = { id: 'acme-2026-10', name: 'Acme VPS', - slots: ['dashboard', 'sidebar', 'page', 'login'], + slots: ['dashboard', 'page', 'login'], until: '2099-01-01T00:00:00Z', title: { en: 'Acme VPS — fast NVMe servers', fa: 'سرورهای سریع Acme' }, text: { en: 'Deploy 3X-UI in 60 seconds. 20% off for panel users.' }, @@ -57,9 +57,8 @@ const meta = { argTypes: { sponsor: { description: 'Sponsor entry from GET /sponsors.' }, 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).' }, }, args: { sponsor }, @@ -81,7 +80,7 @@ export const Banner: Story = { export const Compact: Story = { args: { variant: 'compact', onClose: () => {} }, render: (args) => ( -
+
), @@ -108,23 +107,6 @@ export const BannerLongCopy: Story = { }, }; -export const SidebarLongCopy: Story = { - args: { variant: 'compact', sponsor: longSponsor, onClose: () => {} }, - render: (args) => ( -
- -
- ), - play: async ({ canvasElement }) => { - await expectNotClipped(canvasElement, '.sponsor-title'); - await expectInsideCard(canvasElement, '.sponsor-text'); - await expect(part(canvasElement, '.sponsor-main')).toHaveAttribute( - 'title', - `${longSponsor.title.en}\n${longSponsor.text.en}`, - ); - }, -}; - export const LoginLongCopy: Story = { args: { variant: 'compact', sponsor: longSponsor, onClose: () => {} }, render: (args) => ( @@ -135,6 +117,10 @@ export const LoginLongCopy: Story = { play: async ({ canvasElement }) => { await expectNotClipped(canvasElement, '.sponsor-title'); await expectNotClipped(canvasElement, '.sponsor-text'); + await expect(part(canvasElement, '.sponsor-main')).toHaveAttribute( + 'title', + `${longSponsor.title.en}\n${longSponsor.text.en}`, + ); }, }; @@ -151,7 +137,3 @@ export const PageLongCopy: Story = { await expectNotClipped(canvasElement, '.sponsor-title'); }, }; - -export const IconOnly: Story = { - args: { iconOnly: true }, -}; diff --git a/frontend/src/components/sponsor/SponsorCard.tsx b/frontend/src/components/sponsor/SponsorCard.tsx index 957fef224..3247002fb 100644 --- a/frontend/src/components/sponsor/SponsorCard.tsx +++ b/frontend/src/components/sponsor/SponsorCard.tsx @@ -12,7 +12,6 @@ export type SponsorCardVariant = 'banner' | 'compact' | 'card'; export interface SponsorCardProps { sponsor: Sponsor; variant?: SponsorCardVariant; - iconOnly?: boolean; 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 lang = i18n.resolvedLanguage || i18n.language || 'en'; const title = pickLocale(sponsor.title, lang) || sponsor.name; const text = pickLocale(sponsor.text, lang); const tag = t('pages.sponsors.tag'); - if (iconOnly) { - return ( - - - - ); - } - return (
!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; - const sponsor = visible[(rotate ? index : 0) % visible.length]; + const sponsor = visible[0]; return (
{ dismissSponsor(sponsor.id, slot); setDismissTick((n) => n + 1); diff --git a/frontend/src/layouts/AppSidebar.css b/frontend/src/layouts/AppSidebar.css index 6f47a0888..ddd101628 100644 --- a/frontend/src/layouts/AppSidebar.css +++ b/frontend/src/layouts/AppSidebar.css @@ -247,10 +247,6 @@ padding: 8px 8px 12px; } -.sider-sponsor { - margin-bottom: 6px; -} - .sidebar-pin { display: inline-flex; align-items: center; diff --git a/frontend/src/layouts/AppSidebar.tsx b/frontend/src/layouts/AppSidebar.tsx index 8e3c713a6..57198d771 100644 --- a/frontend/src/layouts/AppSidebar.tsx +++ b/frontend/src/layouts/AppSidebar.tsx @@ -44,7 +44,6 @@ import { formatPanelVersion } from '@/lib/panel-version'; import { pauseAnimationsUntilLeave, useTheme } from '@/hooks/useTheme'; import { useAllSettings } from '@/api/queries/useAllSettings'; import { useCommandPalette } from '@/components/command-palette/useCommandPalette'; -import SponsorSlot from '@/components/sponsor/SponsorSlot'; import './AppSidebar.css'; const DONATE_URL = 'https://donate.sanaei.dev/'; @@ -452,13 +451,6 @@ export default function AppSidebar() { onClick={onMenuClick} />
-
@@ -544,7 +536,6 @@ export default function AppSidebar() { }} />
-
diff --git a/frontend/src/lib/sponsors.ts b/frontend/src/lib/sponsors.ts index 2bd2ef703..403f38e03 100644 --- a/frontend/src/lib/sponsors.ts +++ b/frontend/src/lib/sponsors.ts @@ -1,6 +1,6 @@ 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'; 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)); } catch {} } - -// Mirrors the backend: dashboard/login show one sponsor, the sidebar rotates up to three. -export const SLOT_CAPACITY: Partial> = { - 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] }; -} diff --git a/frontend/src/pages/sponsors/SponsorsPage.css b/frontend/src/pages/sponsors/SponsorsPage.css index 81d26b5a8..6ed958a5f 100644 --- a/frontend/src/pages/sponsors/SponsorsPage.css +++ b/frontend/src/pages/sponsors/SponsorsPage.css @@ -29,30 +29,14 @@ 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 { display: flex; flex-direction: column; - align-items: flex-start; + align-items: center; gap: 8px; - height: 100%; - min-height: 170px; - padding: 16px; + padding: 40px 24px; border: 1.5px dashed var(--ant-color-border); 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; background: var(--bg-card, transparent); } @@ -82,58 +66,6 @@ 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) { .sponsors-page .content-area { padding: 12px; diff --git a/frontend/src/pages/sponsors/SponsorsPage.tsx b/frontend/src/pages/sponsors/SponsorsPage.tsx index bbf0a67cb..2ef55a5e0 100644 --- a/frontend/src/pages/sponsors/SponsorsPage.tsx +++ b/frontend/src/pages/sponsors/SponsorsPage.tsx @@ -1,22 +1,13 @@ import { useMemo } from 'react'; -import type { ReactNode } from 'react'; 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 AppSidebar from '@/layouts/AppSidebar'; import { useSponsorsQuery } from '@/api/queries/useSponsorsQuery'; 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'; 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(); return ( -
+
@@ -56,34 +47,6 @@ export default function SponsorsPage() { const { data, fetched } = useSponsorsQuery(); const sponsors = sponsorsForSlot(data.sponsors, 'page'); - const { datepicker } = useDatepicker(); - const placements: { slot: SponsorSlot; icon: ReactNode; title: string; desc: string }[] = [ - { - slot: 'dashboard', - icon: , - title: t('pages.sponsors.placementDashboard'), - desc: t('pages.sponsors.placementDashboardDesc'), - }, - { - slot: 'sidebar', - icon: , - title: t('pages.sponsors.placementSidebar'), - desc: t('pages.sponsors.placementSidebarDesc'), - }, - { - slot: 'login', - icon: , - title: t('pages.sponsors.placementLogin'), - desc: t('pages.sponsors.placementLoginDesc'), - }, - { - slot: 'page', - icon: , - title: t('pages.sponsors.placementPage'), - desc: t('pages.sponsors.placementPageDesc'), - }, - ]; - const pageClass = useMemo(() => { const classes = ['sponsors-page']; if (isDark) classes.push('is-dark'); @@ -110,63 +73,17 @@ export default function SponsorsPage() { {sponsors.length === 0 ? ( - fetched && + fetched && ) : ( {sponsors.map((sponsor) => ( - + ))} - {data.contact && ( - - - - )} )} - - - {t('pages.sponsors.placements')} - - - {placements.map((p) => { - const status = placementStatus(data.sponsors, p.slot); - return ( - -
- {p.icon} -
-
{p.title}
-
{p.desc}
-
- {status.takenUntil ? ( - - {t('pages.sponsors.takenUntil', { - date: IntlUtil.formatDate(status.takenUntil, datepicker), - })} - - ) : ( - {t('pages.sponsors.available')} - )} - {status.count > 0 && !status.takenUntil && ( - - {t('pages.sponsors.activeCount', { - count: - status.capacity && status.capacity > 1 - ? `${status.count}/${status.capacity}` - : status.count, - })} - - )} -
-
-
- - ); - })} -
diff --git a/frontend/src/test/sponsors.test.tsx b/frontend/src/test/sponsors.test.tsx index 218491101..9d5d469db 100644 --- a/frontend/src/test/sponsors.test.tsx +++ b/frontend/src/test/sponsors.test.tsx @@ -1,12 +1,6 @@ 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(() => { localStorage.clear(); @@ -33,10 +27,10 @@ describe('sponsor dismissal', () => { const t0 = 1_800_000_000_000; 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', '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', () => { @@ -48,51 +42,6 @@ describe('sponsor dismissal', () => { test('corrupt storage is treated as nothing dismissed', () => { 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); }); }); diff --git a/internal/web/service/panel/sponsor.go b/internal/web/service/panel/sponsor.go index 6f9b27a92..d8e6c4ad1 100644 --- a/internal/web/service/panel/sponsor.go +++ b/internal/web/service/panel/sponsor.go @@ -45,7 +45,6 @@ const ( sponsorsErrTTL = 10 * time.Minute maxSponsorsBytes = 256 << 10 maxLogoBytes = 256 << 10 - maxSidebarSlots = 3 localSponsorsDir = "../sponsors/3X" sponsorLogoPath = "/sponsors/logo/" ) @@ -67,7 +66,7 @@ var ( // 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)$`) - 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 sponsorsRaw *SponsorList @@ -217,7 +216,6 @@ func activeSponsors(raw *SponsorList, now time.Time) *SponsorList { if strings.HasPrefix(raw.Contact, "https://") { out.Contact = raw.Contact } - sidebarTaken := 0 for _, sp := range raw.Sponsors { // A missing enable counts as on, so a forgotten field never hides a paid slot. 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)) 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 { continue } - if slices.Contains(slots, "sidebar") { - sidebarTaken++ - } sp.Slots = slots out.Sponsors = append(out.Sponsors, sp) } diff --git a/internal/web/service/panel/sponsor_test.go b/internal/web/service/panel/sponsor_test.go index b2d244c12..c6bfe0c6b 100644 --- a/internal/web/service/panel/sponsor_test.go +++ b/internal/web/service/panel/sponsor_test.go @@ -6,7 +6,6 @@ import ( "net/http/httptest" "os" "path/filepath" - "slices" "strings" "sync/atomic" "testing" @@ -22,7 +21,7 @@ func validSponsor() Sponsor { return Sponsor{ ID: "acme", Name: "Acme", - Slots: []string{"dashboard", "sidebar"}, + Slots: []string{"dashboard"}, Until: sponsorTestNow.Add(24 * time.Hour), Logo: "acme.png", 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) { cases := []struct{ logo, want string }{ {"acme.png", "/sponsors/logo/acme.png"}, @@ -114,7 +85,7 @@ func TestActiveSponsorsLogoName(t *testing.T) { func TestActiveSponsorsResolvesLogoAndSlots(t *testing.T) { sp := validSponsor() - sp.Slots = []string{"subpage", "login"} + sp.Slots = []string{"subpage", "sidebar", "login"} got := activeSponsors(&SponsorList{Contact: "javascript:x", Sponsors: []Sponsor{sp}}, sponsorTestNow) if len(got.Sponsors) != 1 { t.Fatalf("got %d sponsors, want 1", len(got.Sponsors)) diff --git a/internal/web/translation/ar-EG.json b/internal/web/translation/ar-EG.json index ccf5c6c28..16e143831 100644 --- a/internal/web/translation/ar-EG.json +++ b/internal/web/translation/ar-EG.json @@ -2423,19 +2423,7 @@ "tag": "راعٍ", "visit": "زيارة", "yourBrand": "علامتك التجارية هنا", - "yourBrandText": "ضع شركتك أمام آلاف مسؤولي الخوادم الذين يفتحون 3X-UI كل يوم.", - "placements": "المواضع المتاحة", - "placementDashboard": "لافتة لوحة المعلومات", - "placementDashboardDesc": "موضع مميز أعلى لوحة المعلومات، راعٍ واحد في كل مرة.", - "placementSidebar": "بطاقة الشريط الجانبي", - "placementSidebarDesc": "تظهر في كل صفحات اللوحة وتتناوب بين الرعاة.", - "placementLogin": "صفحة تسجيل الدخول", - "placementLoginDesc": "يراها المسؤول عند كل تسجيل دخول.", - "placementPage": "صفحة الرعاة", - "placementPageDesc": "مدرجة هنا مع الشعار والوصف والرابط.", - "available": "متاح", - "takenUntil": "محجوز حتى {date}", - "activeCount": "{count} نشط" + "yourBrandText": "ضع شركتك أمام آلاف مسؤولي الخوادم الذين يفتحون 3X-UI كل يوم." } }, "tgbot": { diff --git a/internal/web/translation/en-US.json b/internal/web/translation/en-US.json index 1e36a4f10..97290aa34 100644 --- a/internal/web/translation/en-US.json +++ b/internal/web/translation/en-US.json @@ -2423,19 +2423,7 @@ "tag": "Sponsor", "visit": "Visit", "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": { diff --git a/internal/web/translation/es-ES.json b/internal/web/translation/es-ES.json index a7ecf7081..d6953176c 100644 --- a/internal/web/translation/es-ES.json +++ b/internal/web/translation/es-ES.json @@ -2423,19 +2423,7 @@ "tag": "Patrocinador", "visit": "Visitar", "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": { diff --git a/internal/web/translation/fa-IR.json b/internal/web/translation/fa-IR.json index da42d9691..3a4e934c3 100644 --- a/internal/web/translation/fa-IR.json +++ b/internal/web/translation/fa-IR.json @@ -2423,19 +2423,7 @@ "tag": "اسپانسر", "visit": "مشاهده", "yourBrand": "جای برند شما", - "yourBrandText": "شرکت خود را جلوی چشم هزاران ادمین سروری بگذارید که هر روز 3X-UI را باز می‌کنند.", - "placements": "جایگاه‌های موجود", - "placementDashboard": "بنر داشبورد", - "placementDashboardDesc": "جایگاه ویژه بالای صفحه‌ی داشبورد، هر بار فقط یک اسپانسر.", - "placementSidebar": "کارت سایدبار", - "placementSidebarDesc": "در همه‌ی صفحات پنل نمایش داده می‌شود و بین اسپانسرها چرخش دارد.", - "placementLogin": "صفحه‌ی ورود", - "placementLoginDesc": "ادمین در هر بار ورود آن را می‌بیند.", - "placementPage": "صفحه‌ی اسپانسرها", - "placementPageDesc": "همراه با لوگو، توضیحات و لینک در همین صفحه.", - "available": "خالی", - "takenUntil": "پر شده تا {date}", - "activeCount": "{count} فعال" + "yourBrandText": "شرکت خود را جلوی چشم هزاران ادمین سروری بگذارید که هر روز 3X-UI را باز می‌کنند." } }, "tgbot": { diff --git a/internal/web/translation/id-ID.json b/internal/web/translation/id-ID.json index 024054197..1af42df6f 100644 --- a/internal/web/translation/id-ID.json +++ b/internal/web/translation/id-ID.json @@ -2423,19 +2423,7 @@ "tag": "Sponsor", "visit": "Kunjungi", "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": { diff --git a/internal/web/translation/ja-JP.json b/internal/web/translation/ja-JP.json index 2ad0a2f8c..d8927f102 100644 --- a/internal/web/translation/ja-JP.json +++ b/internal/web/translation/ja-JP.json @@ -2423,19 +2423,7 @@ "tag": "スポンサー", "visit": "訪問", "yourBrand": "ここにあなたのブランドを", - "yourBrandText": "毎日 3X-UI を開く何千人ものサーバー管理者に御社をアピールできます。", - "placements": "掲載枠", - "placementDashboard": "ダッシュボードバナー", - "placementDashboardDesc": "ダッシュボード上部のプレミアム枠。一度に 1 社のみ。", - "placementSidebar": "サイドバーカード", - "placementSidebarDesc": "パネルの全ページに表示され、スポンサー間でローテーションします。", - "placementLogin": "ログインページ", - "placementLoginDesc": "管理者がログインするたびに表示されます。", - "placementPage": "スポンサーページ", - "placementPageDesc": "ロゴ、説明、リンク付きでここに掲載されます。", - "available": "空きあり", - "takenUntil": "{date} まで予約済み", - "activeCount": "{count} 件掲載中" + "yourBrandText": "毎日 3X-UI を開く何千人ものサーバー管理者に御社をアピールできます。" } }, "tgbot": { diff --git a/internal/web/translation/pt-BR.json b/internal/web/translation/pt-BR.json index e62c85309..b78050110 100644 --- a/internal/web/translation/pt-BR.json +++ b/internal/web/translation/pt-BR.json @@ -2423,19 +2423,7 @@ "tag": "Patrocinador", "visit": "Visitar", "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": { diff --git a/internal/web/translation/ru-RU.json b/internal/web/translation/ru-RU.json index b8b4029bc..59c308fb4 100644 --- a/internal/web/translation/ru-RU.json +++ b/internal/web/translation/ru-RU.json @@ -2423,19 +2423,7 @@ "tag": "Спонсор", "visit": "Перейти", "yourBrand": "Здесь может быть ваш бренд", - "yourBrandText": "Покажите свою компанию тысячам администраторов серверов, которые каждый день открывают 3X-UI.", - "placements": "Доступные места", - "placementDashboard": "Баннер на главной", - "placementDashboardDesc": "Премиум-место вверху панели, один спонсор за раз.", - "placementSidebar": "Карточка в боковой панели", - "placementSidebarDesc": "Показывается на всех страницах панели, спонсоры чередуются.", - "placementLogin": "Страница входа", - "placementLoginDesc": "Администратор видит её при каждом входе.", - "placementPage": "Страница спонсоров", - "placementPageDesc": "Размещение здесь с логотипом, описанием и ссылкой.", - "available": "Свободно", - "takenUntil": "Занято до {date}", - "activeCount": "Активных: {count}" + "yourBrandText": "Покажите свою компанию тысячам администраторов серверов, которые каждый день открывают 3X-UI." } }, "tgbot": { diff --git a/internal/web/translation/tr-TR.json b/internal/web/translation/tr-TR.json index 1d487bcb7..656485cf4 100644 --- a/internal/web/translation/tr-TR.json +++ b/internal/web/translation/tr-TR.json @@ -2423,19 +2423,7 @@ "tag": "Sponsor", "visit": "Ziyaret et", "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": { diff --git a/internal/web/translation/uk-UA.json b/internal/web/translation/uk-UA.json index 991e112b5..f365d3f08 100644 --- a/internal/web/translation/uk-UA.json +++ b/internal/web/translation/uk-UA.json @@ -2423,19 +2423,7 @@ "tag": "Спонсор", "visit": "Перейти", "yourBrand": "Тут може бути ваш бренд", - "yourBrandText": "Покажіть свою компанію тисячам адміністраторів серверів, які щодня відкривають 3X-UI.", - "placements": "Доступні місця", - "placementDashboard": "Банер на головній", - "placementDashboardDesc": "Преміум-місце вгорі панелі, один спонсор за раз.", - "placementSidebar": "Картка в бічній панелі", - "placementSidebarDesc": "Показується на всіх сторінках панелі, спонсори чергуються.", - "placementLogin": "Сторінка входу", - "placementLoginDesc": "Адміністратор бачить її під час кожного входу.", - "placementPage": "Сторінка спонсорів", - "placementPageDesc": "Розміщення тут із логотипом, описом і посиланням.", - "available": "Вільно", - "takenUntil": "Зайнято до {date}", - "activeCount": "Активних: {count}" + "yourBrandText": "Покажіть свою компанію тисячам адміністраторів серверів, які щодня відкривають 3X-UI." } }, "tgbot": { diff --git a/internal/web/translation/vi-VN.json b/internal/web/translation/vi-VN.json index 6df8d3ac7..98a6ddcab 100644 --- a/internal/web/translation/vi-VN.json +++ b/internal/web/translation/vi-VN.json @@ -2423,19 +2423,7 @@ "tag": "Tài trợ", "visit": "Truy cập", "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": { diff --git a/internal/web/translation/zh-CN.json b/internal/web/translation/zh-CN.json index a083b48bb..816bf5ee1 100644 --- a/internal/web/translation/zh-CN.json +++ b/internal/web/translation/zh-CN.json @@ -2423,19 +2423,7 @@ "tag": "赞助", "visit": "访问", "yourBrand": "您的品牌展示位", - "yourBrandText": "让每天打开 3X-UI 的数千名服务器管理员看到您的公司。", - "placements": "可用展示位", - "placementDashboard": "概览横幅", - "placementDashboardDesc": "仪表盘顶部的高级位置,每次仅一家赞助商。", - "placementSidebar": "侧边栏卡片", - "placementSidebarDesc": "在面板所有页面显示,并在赞助商之间轮换。", - "placementLogin": "登录页", - "placementLoginDesc": "管理员每次登录时都能看到。", - "placementPage": "赞助商页面", - "placementPageDesc": "在此页面展示徽标、简介和链接。", - "available": "可预订", - "takenUntil": "已预订至 {date}", - "activeCount": "{count} 个进行中" + "yourBrandText": "让每天打开 3X-UI 的数千名服务器管理员看到您的公司。" } }, "tgbot": { diff --git a/internal/web/translation/zh-TW.json b/internal/web/translation/zh-TW.json index 7a22cbca5..fc1949802 100644 --- a/internal/web/translation/zh-TW.json +++ b/internal/web/translation/zh-TW.json @@ -2423,19 +2423,7 @@ "tag": "贊助", "visit": "造訪", "yourBrand": "您的品牌展示位", - "yourBrandText": "讓每天打開 3X-UI 的數千名伺服器管理員看到您的公司。", - "placements": "可用展示位", - "placementDashboard": "總覽橫幅", - "placementDashboardDesc": "儀表板頂部的高級位置,每次僅一家贊助商。", - "placementSidebar": "側邊欄卡片", - "placementSidebarDesc": "在面板所有頁面顯示,並在贊助商之間輪換。", - "placementLogin": "登入頁", - "placementLoginDesc": "管理員每次登入時都能看到。", - "placementPage": "贊助商頁面", - "placementPageDesc": "在此頁面展示標誌、簡介與連結。", - "available": "可預訂", - "takenUntil": "已預訂至 {date}", - "activeCount": "{count} 個進行中" + "yourBrandText": "讓每天打開 3X-UI 的數千名伺服器管理員看到您的公司。" } }, "tgbot": {