diff --git a/common/constants.go b/common/constants.go index d6b4fb52..cf468089 100644 --- a/common/constants.go +++ b/common/constants.go @@ -12,7 +12,7 @@ import ( var StartTime = time.Now().Unix() // unit: second var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change -var SystemName = "New API" +var SystemName = "LFAPI" var Footer = "" var Logo = "" var TopUpLink = "" diff --git a/common/email_test.go b/common/email_test.go index 7ae45ae3..09f4fac2 100644 --- a/common/email_test.go +++ b/common/email_test.go @@ -281,7 +281,7 @@ func TestSendEmailUsesExplicitStartTLSWithInsecureCertificate(t *testing.T) { SMTPAccount = "sender@example.com" SMTPFrom = "sender@example.com" SMTPToken = "secret" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "
123456
") require.NoError(t, err) @@ -309,7 +309,7 @@ func TestSendEmailExplicitStartTLSRequiresServerSupport(t *testing.T) { SMTPAccount = "sender@example.com" SMTPFrom = "sender@example.com" SMTPToken = "secret" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "123456
") require.Error(t, err) @@ -330,7 +330,7 @@ func TestSendEmailDoesNotAutoUpgradeWhenStartTLSDisabled(t *testing.T) { SMTPAccount = "sender@example.com" SMTPFrom = "sender@example.com" SMTPToken = "secret" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "123456
") require.NoError(t, err) @@ -433,7 +433,7 @@ func TestSendEmailSkipsAuthWhenCredentialsAreEmpty(t *testing.T) { SMTPAccount = "" SMTPFrom = "sender@example.com" SMTPToken = "" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "123456
") require.NoError(t, err) @@ -466,7 +466,7 @@ func TestSendEmailSkipsAuthWhenCredentialsAreIncomplete(t *testing.T) { SMTPAccount = "sender@example.com" SMTPFrom = "sender@example.com" SMTPToken = "" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "123456
") require.NoError(t, err) @@ -500,7 +500,7 @@ func TestSendEmailUsesNTLMWhenServerOnlySupportsNTLM(t *testing.T) { SMTPAccount = "no-reply" SMTPFrom = "no-reply@example.com" SMTPToken = "secret" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "123456
") require.NoError(t, err) @@ -528,7 +528,7 @@ func TestSendEmailUsesNTLMForMicrosoftAccountWhenServerOnlySupportsNTLM(t *testi SMTPAccount = "no-reply@contoso.onmicrosoft.com" SMTPFrom = "no-reply@contoso.onmicrosoft.com" SMTPToken = "secret" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "123456
") require.NoError(t, err) @@ -555,7 +555,7 @@ func TestSendEmailExplicitStartTLSRejectsUntrustedCertificateByDefault(t *testin SMTPAccount = "sender@example.com" SMTPFrom = "sender@example.com" SMTPToken = "secret" - SystemName = "New API" + SystemName = "LFAPI" err := SendEmail("Verification", "receiver@example.com", "123456
") require.Error(t, err) diff --git a/controller/channel.go b/controller/channel.go index 19ddca8e..2b3baaea 100644 --- a/controller/channel.go +++ b/controller/channel.go @@ -417,8 +417,7 @@ func GetChannel(c *gin.Context) { return } -// GetChannelKey 获取渠道密钥(需要通过安全验证中间件) -// 此函数依赖 SecureVerificationRequired 中间件,确保用户已通过安全验证 +// GetChannelKey 获取渠道密钥。仅 Root 管理员可访问该路由。 func GetChannelKey(c *gin.Context) { channelId, err := strconv.Atoi(c.Param("id")) if err != nil { diff --git a/main.go b/main.go index ac4e6afc..e9f6bbc0 100644 --- a/main.go +++ b/main.go @@ -209,8 +209,12 @@ func main() { port = strconv.Itoa(*common.Port) } + listenAddr := strings.TrimSpace(os.Getenv("LISTEN_ADDR")) + if listenAddr == "" { + listenAddr = "0.0.0.0" + } srv := &http.Server{ - Addr: ":" + port, + Addr: listenAddr + ":" + port, Handler: server, } diff --git a/router/channel-router.go b/router/channel-router.go index b85cbd88..ca75ec44 100644 --- a/router/channel-router.go +++ b/router/channel-router.go @@ -24,7 +24,6 @@ func registerChannelRoutes(apiRouter *gin.RouterGroup) { middleware.RootAuth(), middleware.CriticalRateLimit(), middleware.DisableCache(), - middleware.SecureVerificationRequired(), controller.GetChannelKey, ) diff --git a/setting/operation_setting/general_setting.go b/setting/operation_setting/general_setting.go index b4a3cccc..715bdfdc 100644 --- a/setting/operation_setting/general_setting.go +++ b/setting/operation_setting/general_setting.go @@ -24,7 +24,7 @@ type GeneralSetting struct { // 默认配置 var generalSetting = GeneralSetting{ - DocsLink: "https://docs.newapi.pro", + DocsLink: "/docs", PingIntervalEnabled: false, PingIntervalSeconds: 60, QuotaDisplayType: QuotaDisplayTypeUSD, diff --git a/web/index.html b/web/index.html index 7f280b40..1b956efb 100644 --- a/web/index.html +++ b/web/index.html @@ -2,16 +2,16 @@ - + -;U!y2i*S zRexVqy_{ga{m~%d%7gkurAF~l919f5+yJ(Zs$bu>qM&aZ;#}V%@*MK6$h}f$k$XMU zAIO6KH>rHLj!)x1cO2C;Cy>w1!972QDqa@(+U(q5O7lPEY1UG8mD*6Es7E4I)Jv}7 zY>~I^4P;?&lc})RC{?ev_JBV$%l7BjWCsW`0&0NykUt;xyx+x$%iVetzjwVw{@xYH z%iVyy)E&s;?iQlB`}fNEYTNt%K)UMzgOGO6Ul6m9i?+dlEdu2T-UgKCWe2d2aQC(O zuFJf_i&CW>qlnT@V8b=!udlriRLT1nxjWsfYb3OM?e`OV?>7jADwmysd)0=(9rP95 zw0t}d8J?*G`akAAGRg(;D`4j7p?^xM{b)F$US~$ z-{-N i!4{wBrA2vkHE%p>v;mt z5t-_J`!y{e&r_yc1FS7+Sy|p9#q?JTFqO9iTMa;YtE=Q+Egrk}>3Va49S`qV4* z0XVYB3Sb0ROAbAbQ&0OS^o9ZMZ+#;S5%+ z^RB?TJ*hstEk{?=IxkX=`)q)92YpOTlOz-4dXZpUBy6u%vM!+{{KHjQTiIWlo?nLd z%~3qZ>2yC~AKp_5X?oTx)%(TNgg)(=mAe1RN|&~{@8O7K6X%j^VqJG(e+0zqURLgn tU3&WMR0Gpu|9Ei8jfYpBb`7V#GXl;CI3wVUfHMNl2sk6)jKG5k{0~Ee%>)1d diff --git a/web/src/assets/logo.tsx b/web/src/assets/logo.tsx index 1f9dd232..8237d266 100644 --- a/web/src/assets/logo.tsx +++ b/web/src/assets/logo.tsx @@ -36,7 +36,7 @@ export function Logo({ className, ...props }: SVGProps ) { className={cn('size-6', className)} {...props} > - New API +LFAPI ) diff --git a/web/src/components/layout/components/authenticated-layout.tsx b/web/src/components/layout/components/authenticated-layout.tsx index 237031a2..295cda20 100644 --- a/web/src/components/layout/components/authenticated-layout.tsx +++ b/web/src/components/layout/components/authenticated-layout.tsx @@ -37,7 +37,7 @@ export function AuthenticatedLayout(props: AuthenticatedLayoutProps) { return ( - + diff --git a/web/src/components/layout/components/footer.tsx b/web/src/components/layout/components/footer.tsx index 438fc6cf..3cc73c6b 100644 --- a/web/src/components/layout/components/footer.tsx +++ b/web/src/components/layout/components/footer.tsx @@ -42,12 +42,6 @@ interface FooterProps { className?: string } -const NEW_API_FOOTER_ATTRIBUTION_KEY = [ - 'footer', - 'new' + 'api', - 'projectAttributionSuffix', -].join('.') - function FooterLinkItem(props: { link: FooterLink }) { const { t } = useTranslation() const isExternal = props.link.href.startsWith('http') @@ -129,14 +123,11 @@ function ProjectAttribution(props: { currentYear: number; inline?: boolean }) { © {props.currentYear}{' '} - {t('New API')} + LFAPI 由 NewAPI 二次开发而成 - . {t(NEW_API_FOOTER_ATTRIBUTION_KEY)} ) if (props.inline) { @@ -158,8 +149,8 @@ export function Footer(props: FooterProps) { demoSiteEnabled, } = useSystemConfig() - const displayLogo = systemLogo || props.logo || '/logo.png' - const displayName = systemName || props.name || 'New API' + const displayLogo = systemLogo || props.logo || '/lfapi-logo.svg' + const displayName = systemName || props.name || 'LFAPI' const isDemoSiteMode = Boolean(demoSiteEnabled) const currentYear = new Date().getFullYear() @@ -170,15 +161,15 @@ export function Footer(props: FooterProps) { links: [ { text: t('footer.columns.about.links.aboutProject'), - href: 'https://docs.newapi.pro/wiki/project-introduction/', + href: '/docs/zh/docs/guide/wiki/basic-concepts/project-introduction', }, { text: t('footer.columns.about.links.contact'), - href: 'https://docs.newapi.pro/support/community-interaction/', + href: '/docs/zh/docs/support/community-interaction', }, { text: t('footer.columns.about.links.features'), - href: 'https://docs.newapi.pro/wiki/features-introduction/', + href: '/docs/zh/docs/guide/wiki/features-introduction', }, ], }, @@ -187,15 +178,15 @@ export function Footer(props: FooterProps) { links: [ { text: t('footer.columns.docs.links.quickStart'), - href: 'https://docs.newapi.pro/getting-started/', + href: '/docs/zh/docs/getting-started', }, { text: t('footer.columns.docs.links.installation'), - href: 'https://docs.newapi.pro/installation/', + href: '/docs/zh/docs/installation', }, { text: t('footer.columns.docs.links.apiDocs'), - href: 'https://docs.newapi.pro/api/', + href: '/docs/zh/docs/api', }, ], }, diff --git a/web/src/components/layout/components/header-logo.tsx b/web/src/components/layout/components/header-logo.tsx index 2ef2b84a..03b48ed4 100644 --- a/web/src/components/layout/components/header-logo.tsx +++ b/web/src/components/layout/components/header-logo.tsx @@ -42,7 +42,7 @@ export function HeaderLogo({ src={src} alt={alt} className={cn( - 'h-6 w-6 rounded-full transition-opacity duration-200', + 'h-6 w-6 rounded-lg transition-opacity duration-200', !loading && logoLoaded ? 'opacity-100' : 'opacity-0', className )} diff --git a/web/src/components/layout/components/public-layout.tsx b/web/src/components/layout/components/public-layout.tsx index bcb65dbe..b8fbad37 100644 --- a/web/src/components/layout/components/public-layout.tsx +++ b/web/src/components/layout/components/public-layout.tsx @@ -34,7 +34,7 @@ type PublicLayoutProps = { export function PublicLayout(props: PublicLayoutProps) { return ( -+- +{name} @@ -89,7 +89,7 @@ export function SystemBrand(props: SystemBrandProps) {![]()
diff --git a/web/src/features/about/index.tsx b/web/src/features/about/index.tsx index 22834305..8aa1b660 100644 --- a/web/src/features/about/index.tsx +++ b/web/src/features/about/index.tsx @@ -47,62 +47,21 @@ function EmptyAboutState() {- {t('New API Project Repository:')}{' '} - - {t('https://github.com/QuantumNous/new-api')} + LFAPI 源码与许可证:{' '} + + /source
- - {t('NewAPI')} - {' '} - © {currentYear}{' '} - - {t('QuantumNous')} - {' '} - {t('| Based on')}{' '} - - {t('One API')} - {' '} - © 2023{' '} - - {t('JustSong')} - + LFAPI 由 NewAPI 二次开发而成。© {currentYear}
{t('This project must be used in compliance with the')}{' '} - {t('AGPL v3.0 License')} + AGPL v3.0 License .
diff --git a/web/src/features/auth/auth-layout.tsx b/web/src/features/auth/auth-layout.tsx index ba3a5bf9..50d0e857 100644 --- a/web/src/features/auth/auth-layout.tsx +++ b/web/src/features/auth/auth-layout.tsx @@ -31,7 +31,7 @@ export function AuthLayout({ children }: AuthLayoutProps) { const { systemName, logo, loading } = useSystemConfig() return ( -+)}diff --git a/web/src/features/channels/api.ts b/web/src/features/channels/api.ts index fa13a186..b898a405 100644 --- a/web/src/features/channels/api.ts +++ b/web/src/features/channels/api.ts @@ -301,18 +301,17 @@ export async function deleteDisabledChannels(): Promise<{ } /** - * Get channel key (requires 2FA verification) + * Get channel key (available to the Root administrator) */ -export async function getChannelKey( - id: number, - proofToken?: string -): Promise<{ success: boolean; message?: string; data?: { key: string } }> { +export async function getChannelKey(id: number): Promise<{ + success: boolean + message?: string + data?: { key: string } +}> { const res = await api.post( `/api/channel/${id}/key`, undefined, - channelActionConfig({ - headers: proofToken ? { 'X-Security-Proof': proofToken } : undefined, - }) + channelActionConfig() ) return res.data } diff --git a/web/src/features/channels/components/drawers/channel-mutate-drawer.tsx b/web/src/features/channels/components/drawers/channel-mutate-drawer.tsx index 93f6a0a1..8f5e859c 100644 --- a/web/src/features/channels/components/drawers/channel-mutate-drawer.tsx +++ b/web/src/features/channels/components/drawers/channel-mutate-drawer.tsx @@ -107,10 +107,6 @@ import { TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip' -import { - SecureVerificationDialog, - useSecureVerification, -} from '@/features/auth/secure-verification' import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' import { useHiddenClickUnlock } from '@/hooks/use-hidden-click-unlock' import { @@ -695,17 +691,6 @@ export function ChannelMutateDrawer({ const { copyToClipboard } = useCopyToClipboard() - const { - open: verificationOpen, - methods: verificationMethods, - state: verificationState, - executeVerification, - withVerification, - cancel: cancelVerification, - setCode: setVerificationCode, - switchMethod: switchVerificationMethod, - } = useSecureVerification() - useEffect(() => { if (!open) { setChannelKey(null) @@ -1373,14 +1358,14 @@ export function ChannelMutateDrawer({ } const fetchChannelKey = useCallback( - async (proofToken?: string) => { + async () => { if (!channelId) { throw new Error('Channel is not selected') } setIsChannelKeyLoading(true) try { - const res = await getChannelKey(channelId, proofToken) + const res = await getChannelKey(channelId) if (!res.success) { throw new Error(res.message || t('Failed to fetch channel key')) } @@ -1400,20 +1385,13 @@ export function ChannelMutateDrawer({ if (!channelId) return try { - await withVerification(fetchChannelKey, { - scope: 'channel.key.read', - preferredMethod: 'passkey', - title: t('Verify to view channel key'), - description: t( - 'Use Passkey or 2FA to confirm your identity before revealing this channel key.' - ), - }) + await fetchChannelKey() } catch (error) { if (error instanceof Error) { toast.error(error.message) } } - }, [channelId, withVerification, fetchChannelKey, t]) + }, [channelId, fetchChannelKey]) const handleRefreshCodexCredential = useCallback(async () => { if (!channelId) return @@ -3119,9 +3097,7 @@ export function ChannelMutateDrawer({ {t('Current key')}- {t( - 'Verification required to reveal the saved key.' - )} + {t('Display and copy the saved key.')}
@@ -3130,13 +3106,9 @@ export function ChannelMutateDrawer({ variant='outline' size='sm' onClick={handleRevealKey} - disabled={ - isChannelKeyLoading || - verificationState.loading - } + disabled={isChannelKeyLoading} > - {isChannelKeyLoading || - verificationState.loading ? ( + {isChannelKeyLoading ? () : ( @@ -3165,7 +3137,7 @@ export function ChannelMutateDrawer({ readOnly value={channelKey ?? ''} placeholder={t( - 'Hidden — verify to reveal' + 'Hidden — select reveal to display' )} className='font-mono' /> @@ -4931,23 +4903,6 @@ export function ChannelMutateDrawer({ existingModelsOverride={currentModelsArray} /> - { - if (!open) { - cancelVerification() - } - }} - methods={verificationMethods} - state={verificationState} - onVerify={async (method, code) => { - await executeVerification(method, code) - }} - onCancel={cancelVerification} - onCodeChange={setVerificationCode} - onMethodChange={switchVerificationMethod} - /> - {/* Missing Models Confirmation Dialog */} + {/* Gradient mesh background */} + diff --git a/web/src/features/home/components/sections/how-it-works.tsx b/web/src/features/home/components/sections/how-it-works.tsx index 2959fd87..fa8150c3 100644 --- a/web/src/features/home/components/sections/how-it-works.tsx +++ b/web/src/features/home/components/sections/how-it-works.tsx @@ -50,7 +50,7 @@ export function HowItWorks() { ] return ( -diff --git a/web/src/features/home/components/sections/hero.tsx b/web/src/features/home/components/sections/hero.tsx index 4a08151a..59aab528 100644 --- a/web/src/features/home/components/sections/hero.tsx +++ b/web/src/features/home/components/sections/hero.tsx @@ -49,7 +49,7 @@ export function Hero(props: HeroProps) { const { t } = useTranslation() const { status } = useStatus() const docsUrl = - (status?.docs_link as string | undefined) || 'https://docs.newapi.pro' + (status?.docs_link as string | undefined) || '/docs' const renderDocsButton = () => { const isExternal = docsUrl.startsWith('http') @@ -180,7 +180,7 @@ export function Hero(props: HeroProps) {
{t( - 'Supports one-click configuration and perfectly adapts to NewAPI multi-protocol configuration.' + 'Supports one-click configuration and multi-protocol configuration.' )}
+ diff --git a/web/src/features/home/components/sections/stats.tsx b/web/src/features/home/components/sections/stats.tsx index c3a5bc1d..7639a2a2 100644 --- a/web/src/features/home/components/sections/stats.tsx +++ b/web/src/features/home/components/sections/stats.tsx @@ -105,7 +105,7 @@ export function Stats(_props: StatsProps) { ] return ( -
+{stats.map((s) => ( diff --git a/web/src/features/system-settings/auth/passkey-section.tsx b/web/src/features/system-settings/auth/passkey-section.tsx index c2296196..5858a0c1 100644 --- a/web/src/features/system-settings/auth/passkey-section.tsx +++ b/web/src/features/system-settings/auth/passkey-section.tsx @@ -225,7 +225,7 @@ export function PasskeySection(props: PasskeySectionProps) {{t('Relying Party Display Name')} field.onChange(event.target.value)} name={field.name} diff --git a/web/src/features/system-settings/general/system-info-section.tsx b/web/src/features/system-settings/general/system-info-section.tsx index 3758b369..18d3c48b 100644 --- a/web/src/features/system-settings/general/system-info-section.tsx +++ b/web/src/features/system-settings/general/system-info-section.tsx @@ -154,7 +154,7 @@ export function SystemInfoSection({ defaultValues }: SystemInfoSectionProps) { {t('System Name')} - + {t('The name displayed across the application')} @@ -282,7 +282,7 @@ export function SystemInfoSection({ defaultValues }: SystemInfoSectionProps) { {t('Home Page Content')} diff --git a/web/src/features/system-settings/integrations/email-settings-section.tsx b/web/src/features/system-settings/integrations/email-settings-section.tsx index 67cb1e25..e89ca30b 100644 --- a/web/src/features/system-settings/integrations/email-settings-section.tsx +++ b/web/src/features/system-settings/integrations/email-settings-section.tsx @@ -370,7 +370,7 @@ export function EmailSettingsSection({ field.onChange(event.target.value)} /> diff --git a/web/src/features/system-settings/site/index.tsx b/web/src/features/system-settings/site/index.tsx index dffda559..3f9d64d6 100644 --- a/web/src/features/system-settings/site/index.tsx +++ b/web/src/features/system-settings/site/index.tsx @@ -26,7 +26,7 @@ import { const defaultSiteSettings: SiteSettings = { Notice: '', - SystemName: 'New API', + SystemName: 'LFAPI', Logo: '', Footer: '', About: '', diff --git a/web/src/hooks/use-system-config.ts b/web/src/hooks/use-system-config.ts index 23f92a63..b7299d7e 100644 --- a/web/src/hooks/use-system-config.ts +++ b/web/src/hooks/use-system-config.ts @@ -93,8 +93,12 @@ export function mapStatusDataToConfig( } return { - systemName: data.system_name || DEFAULT_SYSTEM_NAME, - logo: data.logo || DEFAULT_LOGO, + // LFAPI branding is intentionally fixed in the public build. Keep the + // upstream system fields untouched in the API/database for compatibility, + // while preventing an old persisted NewAPI logo from overriding the new + // favicon and navigation mark at runtime. + systemName: DEFAULT_SYSTEM_NAME, + logo: DEFAULT_LOGO, footerHtml: data.footer_html, demoSiteEnabled: data.demo_site_enabled, displayTokenStatEnabled: data.display_token_stat_enabled, diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index 0f0c178a..5fe655e6 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -21,8 +21,9 @@ For commercial licensing, please contact support@quantumnous.com */ // System Configuration Defaults -export const DEFAULT_SYSTEM_NAME = 'New API' -export const DEFAULT_LOGO = '/logo.png' +export const DEFAULT_SYSTEM_NAME = 'LFAPI' +// Versioned so a previously cached favicon cannot survive a brand update. +export const DEFAULT_LOGO = '/lfapi-logo.svg?v=lfapi-dopamine-1' // LocalStorage Keys export const STORAGE_KEYS = { diff --git a/web/src/main.tsx b/web/src/main.tsx index b2cf827c..27eee199 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -30,6 +30,7 @@ import { toast } from 'sonner' import { getStatus } from '@/lib/api' import { installBuildMetadata } from '@/lib/build-metadata' +import { DEFAULT_LOGO } from '@/lib/constants' import { applyFaviconToDom } from '@/lib/dom-utils' import '@/lib/dayjs' import { initializeFrontendCache } from '@/lib/frontend-cache' @@ -130,7 +131,6 @@ if (!rootElement) { if (saved) { const s = JSON.parse(saved) if (s?.system_name) apply(s.system_name) - if (s?.logo) applyFaviconToDom(s.logo) } } catch { /* empty */ @@ -146,7 +146,9 @@ if (!rootElement) { /* empty */ } } - if (s?.logo) applyFaviconToDom(s.logo as string) + // A stale localStorage logo used to overwrite the HTML favicon forever + // when the server intentionally had no custom logo configured. + applyFaviconToDom((s?.logo as string) || DEFAULT_LOGO) }) .catch(() => { /* empty */ diff --git a/web/src/styles/index.css b/web/src/styles/index.css index a9eadb5c..c4660d2d 100644 --- a/web/src/styles/index.css +++ b/web/src/styles/index.css @@ -29,6 +29,7 @@ For commercial licensing, please contact support@quantumnous.com @import './theme.css'; @import './theme-presets.css'; +@import './lfapi-dopamine.css'; /* Shiki dual themes: token colors follow dark theme (pre background stays `bg-background` on the block) */ @layer components { @@ -713,6 +714,263 @@ For commercial licensing, please contact support@quantumnous.com animation: auto-group-flow-border-travel 3.2s linear infinite; } +/* LFAPI dopamine visual system. Kept scoped to public/auth surfaces so the + * authenticated product can retain its existing density while sharing the + * same palette and interaction language. */ +@layer components { + .lfapi-public { + --lf-ink: #252640; + --lf-ink-soft: #62647a; + --lf-cream: #fff9ef; + --lf-paper: #fffdf8; + --lf-line: #e8e1d7; + --lf-purple: #6557e8; + --lf-purple-dark: #463bb1; + --lf-coral: #e95f74; + --lf-mint: #d9f2e7; + --lf-yellow: #ffe08a; + background: var(--lf-cream) !important; + color: var(--lf-ink) !important; + } + + .lfapi-public header nav { + color: var(--lf-ink); + } + + .lfapi-public header nav:not(:has(+ *)) { + background-color: transparent; + } + + .lfapi-public header nav .bg-background\/60 { + background: rgba(255, 253, 248, 0.92) !important; + border: 2px solid var(--lf-ink); + box-shadow: 4px 5px 0 var(--lf-yellow) !important; + backdrop-filter: none; + } + + .lfapi-public header nav a, + .lfapi-public header nav button { + color: var(--lf-ink-soft); + } + + .lfapi-public header nav a:hover, + .lfapi-public header nav button:hover { + color: var(--lf-ink) !important; + } + + .lfapi-public header nav [data-slot='button'] { + background: var(--lf-purple) !important; + color: white !important; + border: 2px solid var(--lf-ink); + box-shadow: 0 3px 0 var(--lf-purple-dark); + } + + .lfapi-public > section:first-of-type { + background: var(--lf-cream); + padding-top: clamp(7rem, 13vw, 10rem); + padding-bottom: clamp(4rem, 8vw, 7rem); + } + + .lfapi-public > section:first-of-type > [aria-hidden] { + display: none !important; + } + + .lfapi-public > section:first-of-type h1 { + color: var(--lf-ink); + font-size: clamp(2.55rem, 5.6vw, 5rem) !important; + line-height: 1.05; + letter-spacing: -0.025em; + } + + .lfapi-public > section:first-of-type h1 span { + background: none !important; + color: var(--lf-purple) !important; + -webkit-text-fill-color: currentColor; + } + + .lfapi-public > section:first-of-type p { + color: var(--lf-ink-soft) !important; + } + + .lfapi-public > section:first-of-type .rounded-full.border-blue-500\/20 { + border: 2px solid var(--lf-ink); + border-radius: 12px; + background: var(--lf-coral); + color: var(--lf-ink); + box-shadow: 4px 4px 0 var(--lf-ink); + } + + .lfapi-public > section:first-of-type .rounded-full.border-blue-500\/20 > span:first-child { + display: none; + } + + .lfapi-public [data-slot='button'] { + border-radius: 12px; + border-width: 2px; + border-color: var(--lf-ink); + font-weight: 700; + } + + .lfapi-public [data-slot='button'].bg-primary, + .lfapi-public [data-slot='button']:not(.border-border) { + background: var(--lf-purple) !important; + color: white !important; + box-shadow: 0 5px 0 var(--lf-purple-dark); + } + + .lfapi-public [data-slot='button'].bg-primary:hover, + .lfapi-public [data-slot='button']:not(.border-border):hover { + transform: translateY(-2px); + box-shadow: 0 7px 0 var(--lf-purple-dark); + } + + .lfapi-public [data-slot='button'].border-border { + background: var(--lf-paper) !important; + color: var(--lf-ink) !important; + box-shadow: 0 4px 0 var(--lf-ink); + } + + .lfapi-public > section:first-of-type a.rounded-full, + .lfapi-public > section:first-of-type .cursor-default.rounded-full { + border: 2px solid var(--lf-ink); + border-radius: 14px; + background: var(--lf-mint); + color: var(--lf-ink); + box-shadow: 0 4px 0 var(--lf-ink); + backdrop-filter: none; + } + + .lfapi-public > section:first-of-type a.rounded-full:nth-child(2) { + background: var(--lf-yellow); + } + + .lfapi-public .lfapi-stats { + border-color: var(--lf-ink) !important; + background: var(--lf-yellow) !important; + } + + .lfapi-public .lfapi-stats [class*='text-2xl'], + .lfapi-public .lfapi-stats [class*='text-3xl'] { + color: var(--lf-ink); + } + + .lfapi-public .lfapi-features, + .lfapi-public .lfapi-how-it-works { + background: var(--lf-cream); + } + + .lfapi-public .lfapi-features .group { + border: 2px solid var(--lf-ink); + border-radius: 16px; + background: var(--lf-paper); + box-shadow: 5px 6px 0 var(--lf-ink); + } + + .lfapi-public .lfapi-features .group:nth-child(2) { + background: var(--lf-mint); + } + + .lfapi-public .lfapi-features .group:nth-child(3) { + background: #f0edff; + } + + .lfapi-public .lfapi-how-it-works { + background: var(--lf-mint); + } + + .lfapi-public .lfapi-cta { + background: var(--lf-yellow); + } + + .lfapi-public footer { + border-color: var(--lf-ink) !important; + background: var(--lf-paper); + } + + .lfapi-public .lfapi-cta-mesh { + display: none !important; + } + + .lfapi-public footer img { + border-radius: 10px; + } + + .lfapi-auth { + background: var(--lf-cream); + color: var(--lf-ink); + } + + .lfapi-auth > a { + color: var(--lf-ink); + } + + .lfapi-auth > a img { + border: 2px solid var(--lf-ink); + box-shadow: 3px 3px 0 var(--lf-coral); + } + + .lfapi-auth > div > div { + border: 2px solid var(--lf-ink); + border-radius: 20px; + background: var(--lf-paper); + box-shadow: 8px 8px 0 var(--lf-yellow); + padding: clamp(1.5rem, 4vw, 2.25rem); + } + + .lfapi-auth [data-slot='button'].bg-primary, + .lfapi-auth [data-slot='button']:not(.border-border) { + background: var(--lf-purple) !important; + color: white !important; + border: 2px solid var(--lf-ink); + box-shadow: 0 4px 0 var(--lf-purple-dark); + } + + .lfapi-auth input, + .lfapi-auth select, + .lfapi-auth textarea { + border: 2px solid var(--lf-line); + border-radius: 10px; + background: #fffefa; + } + + .lfapi-auth input:focus, + .lfapi-auth select:focus, + .lfapi-auth textarea:focus { + border-color: var(--lf-purple); + box-shadow: 0 0 0 3px rgba(101, 87, 232, 0.16); + } + + @media (max-width: 860px) { + .lfapi-public > section:first-of-type { + padding-top: 6.5rem; + } + } + + @media (max-width: 560px) { + .lfapi-public > section:first-of-type { + padding-inline: 1.1rem; + } + + .lfapi-public > section:first-of-type h1 { + font-size: 2.6rem !important; + } + + .lfapi-public > section:first-of-type .mt-8 { + width: 100%; + } + + .lfapi-public > section:first-of-type [data-slot='button'] { + width: 100%; + justify-content: center; + } + + .lfapi-auth > div > div { + margin-inline: 0.8rem; + box-shadow: 5px 6px 0 var(--lf-yellow); + } + } +} + @media (prefers-reduced-motion: reduce) { .auto-group-flow-border { display: none; diff --git a/web/src/styles/theme.css b/web/src/styles/theme.css index 53ffcd19..14197cff 100644 --- a/web/src/styles/theme.css +++ b/web/src/styles/theme.css @@ -103,33 +103,33 @@ For commercial licensing, please contact support@quantumnous.com * in theme-presets.css. Default mirrors `--font-sans` so behavior is * identical when no font preference is set. */ --font-body: var(--font-sans); - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.692 0.141 243.716); - --primary-foreground: oklch(1 0 0); - --secondary: oklch(0.95 0 0); - --secondary-foreground: oklch(0.42 0.16 250); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.49 0 0); - --accent: color-mix(in oklch, var(--primary) 12%, var(--background)); - --accent-foreground: oklch(0.145 0 0); - --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: oklch(0.985 0 0); - --success: oklch(0.596 0.145 163.225); - --success-foreground: oklch(0.985 0 0); - --warning: oklch(0.681 0.162 75.834); - --warning-foreground: oklch(0.145 0 0); - --info: oklch(0.588 0.158 241.966); - --info-foreground: oklch(0.985 0 0); - --neutral: oklch(0.708 0 0); - --neutral-foreground: oklch(0.145 0 0); - --border: oklch(0.93 0 0); - --input: oklch(0.93 0 0); - --ring: oklch(0.708 0.16 249.003); + --background: #fff9ef; + --foreground: #252640; + --card: #fffdf8; + --card-foreground: #252640; + --popover: #fffdf8; + --popover-foreground: #252640; + --primary: #6557e8; + --primary-foreground: #ffffff; + --secondary: #eeecff; + --secondary-foreground: #463bb1; + --muted: #f3eee5; + --muted-foreground: #62647a; + --accent: #d9f2e7; + --accent-foreground: #252640; + --destructive: #e95f74; + --destructive-foreground: #ffffff; + --success: #3aa982; + --success-foreground: #ffffff; + --warning: #ffcf4a; + --warning-foreground: #252640; + --info: #6557e8; + --info-foreground: #ffffff; + --neutral: #8d8da0; + --neutral-foreground: #252640; + --border: #e8e1d7; + --input: #ddd7ce; + --ring: #6557e8; --chart-1: oklch(0.72 0.18 250); --chart-2: oklch(0.65 0.15 200); --chart-3: oklch(0.7 0.12 280);