diff --git a/packages/experience/src/Providers/AppBoundary/AppMeta.tsx b/packages/experience/src/Providers/AppBoundary/AppMeta.tsx index 1e0543e9fb..1b099d6716 100644 --- a/packages/experience/src/Providers/AppBoundary/AppMeta.tsx +++ b/packages/experience/src/Providers/AppBoundary/AppMeta.tsx @@ -1,21 +1,22 @@ -import { Theme } from '@logto/schemas'; -import { conditionalString } from '@silverhand/essentials'; -import classNames from 'classnames'; -import i18next from 'i18next'; -import { useContext } from 'react'; -import { Helmet } from 'react-helmet'; +import { Theme } from '@logto/schemas' +import { conditionalString } from '@silverhand/essentials' +import classNames from 'classnames' +import i18next from 'i18next' +import { useContext } from 'react' +import { Helmet } from 'react-helmet' -import PageContext from '@/Providers/PageContextProvider/PageContext'; -import defaultAppleTouchLogo from '@/assets/apple-touch-icon.png'; -import defaultFavicon from '@/assets/favicon.png'; -import { type SignInExperienceResponse } from '@/types'; +import PageContext from '@/Providers/PageContextProvider/PageContext' +import defaultAppleTouchLogo from '@/assets/apple-touch-icon.png' +import defaultFavicon from '@/assets/favicon.png' +import { type SignInExperienceResponse } from '@/types' -import styles from './index.module.scss'; +import styles from './index.module.scss' +import bitfocusCustomStyles from './bitfocus-custom-styles.css?raw' const themeToFavicon = Object.freeze({ - [Theme.Light]: 'favicon', - [Theme.Dark]: 'darkFavicon', -} as const satisfies Record); + [Theme.Light]: 'favicon', + [Theme.Dark]: 'darkFavicon', +} as const satisfies Record) /** * User React Helmet to manage html and body attributes @@ -32,25 +33,26 @@ const themeToFavicon = Object.freeze({ */ const AppMeta = () => { - const { experienceSettings, theme, platform, isPreview } = useContext(PageContext); - const favicon = - experienceSettings?.branding[themeToFavicon[theme]] ?? experienceSettings?.branding.favicon; + const { experienceSettings, theme, platform, isPreview } = useContext(PageContext) + const favicon = experienceSettings?.branding[themeToFavicon[theme]] ?? experienceSettings?.branding.favicon - return ( - - - - - {experienceSettings?.customCss && } - - - ); -}; + return ( + + + + + {/* make sure the experienceSettings.customCss overrides the customStyles */} + {bitfocusCustomStyles && } + {experienceSettings?.customCss && } + + + ) +} -export default AppMeta; +export default AppMeta diff --git a/packages/experience/src/Providers/AppBoundary/bitfocus-custom-styles.css b/packages/experience/src/Providers/AppBoundary/bitfocus-custom-styles.css new file mode 100644 index 0000000000..0a32a88fd0 --- /dev/null +++ b/packages/experience/src/Providers/AppBoundary/bitfocus-custom-styles.css @@ -0,0 +1,241 @@ +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100 900; /* supports variable weights */ + font-display: swap; + src: url('/fonts/inter/Inter-VariableFont_slnt,wght.woff2') format('woff2'); +} + +#app * { + font-family: 'Inter', sans-serif; +} + +/* Main background - matches bg-neutral-950 */ +#app > div[class$='viewBox'] { + background: #0a0a0a; + min-height: 100vh; +} + +#app main[class*='main'] { + background: transparent; + max-width: 460px; + margin: 0 auto; + padding: 64px 16px 40px; + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; +} + +#app main[class*='main'] div[class*='logoWrapper'] { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + margin-bottom: 16px; +} + +#app main[class*='main'] div[class*='headline'] { + color: #d4d4d4; +} + +#app main[class*='main'] img[class*='logo'] { + content: url('src/assets/buttons.png'); + width: 64px; + height: 64px; + margin: 0; +} + +/* Title styling - matches text-3xl font-semibold tracking-tight */ +#app main[class*='main'] h1, +#app main[class*='main'] h2 { + font-size: 1.875rem; + font-weight: 600; + letter-spacing: -0.025em; + color: #d4d4d8; /* text-neutral-300 */ + text-align: center; + margin: 0; +} + +/* Subtitle styling */ +#app main[class*='main'] h2 { + font-size: 1.5rem; + margin-top: 8px; +} + +/* Description text - matches text-sm text-neutral-300 text-center */ +#app main[class*='main'] p { + font-size: 0.875rem; + color: #d4d4d8; /* text-neutral-300 */ + text-align: center; + margin: 16px 0; + line-height: 1.5; +} + +#app main[class*='main'] > div[class*='wrapper'] > div[class*='container'] { + margin: 16px auto; +} + +/* Form container - matches rounded-md border border-neutral-800 bg-neutral-900 p-5 shadow-xl */ +#app main[class*='main'] > div[class*='wrapper'] { + background: #171717; /* bg-neutral-900 */ + border: 1px solid #262626; /* border-neutral-800 */ + border-radius: 6px; /* rounded-md */ + padding: 32px; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); + margin-top: 16px; + flex: 0; +} + +/* Form styling */ +#app form { + display: flex; + flex-direction: column; + gap: 4px; +} + +/* Label styling - matches text-sm font-medium text-neutral-200 */ +#app form label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: #e5e5e5; /* text-neutral-200 */ + margin-bottom: 4px; +} + +/* Input field container */ +#app form div[class*='inputField'] > div { + outline: none; + border: none; + border-radius: 4px; + background: rgba(38, 38, 38, 0.2); /* bg-neutral-800/20 */ +} + +/* Input field action button */ +#app form div[class*='inputField'] > button { + align-items: end; +} + +/* Input styling - matches mt-1 block w-full border-gray-300 rounded-sm shadow-sm bg-neutral-800/20 px-2 py-1 text-lg */ +#app form div[class*='inputField'] input, +#app form div[class*='inputField'] div[class$='countryCodeSelector'] { + background: rgba(38, 38, 38, 0.2); /* bg-neutral-800/20 */ + font-family: 'Inter', sans-serif; + font-size: 1.125rem; /* text-lg */ + font-weight: 400; + color: #d4d4d8; /* text-neutral-300 */ + border: 1px solid #404040; /* subtle border */ + border-radius: 4px; /* rounded-sm */ + padding: 4px 8px; /* px-2 py-1 */ + width: 100%; + box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); +} + +/* Input focus state */ +#app form div[class*='inputField'] input:focus, +#app form div[class*='inputField'] div[class$='countryCodeSelector']:focus { + outline: none; + border-color: #3b82f6; /* focus ring color */ + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +/* Placeholder styling */ +#app form div[class*='inputField'] > div > input::placeholder, +#app main[class*='main'] > div[class*='wrapper'] > div[class*='divider'], +#app main[class*='main'] > div[class*='wrapper'] > form div[class*='content'], +#app main[class*='main'] > div[class*='wrapper'] > form div[class*='content'] > span { + color: #a3a3a3; /* neutral-400 for placeholders */ +} + +/* Error message styling - matches text-red-400 text-xs mt-1 */ +#app form div[class*='error'] { + color: #f87171; /* text-red-400 */ + font-size: 0.75rem; /* text-xs */ + margin-top: 4px; +} + +/* Button styling - matches the submit button from FirstUserPage */ +#app button { + font-weight: 600; + font-size: 1.125rem; /* text-lg */ + border-radius: 6px; /* rounded-md */ + padding: 6px 0; /* py-1.5 */ + width: 100%; + transition: all 0.2s; + border: none; + cursor: pointer; +} + +/* Submit button - matches bg-bitfocus-500 styling */ +#app button[type='submit'] { + background: #F0BE35; + color: #0f172a; /* text-neutral-900 */ + opacity: 0.9; +} + +#app button[type='submit']:hover:not(:disabled) { + opacity: 1; +} + +#app button[type='submit']:disabled { + background: rgba(82, 82, 82, 0.1); /* bg-neutral-500/10 */ + color: #404040; /* text-neutral-700 */ + cursor: not-allowed; +} + +/* Social login buttons */ +#app div[class*='socialLinkList'] > button { + border: none; + background-color: #3b82f6; /* bitfocus-500 */ + color: #0f172a; + margin-bottom: 8px; +} + +/* SSO label */ +#app [class*='singleSignOn'] { + color: #d4d4d8; /* text-neutral-300 */ + font-weight: 400; +} + +#app [class*='navBar'] { + color: #d4d4d8; /* text-neutral-300 */ + font-weight: 800; +} +#app [class*='title'] { + color: #d4d4d8; /* text-neutral-300 */ + font-weight: 800; +} + +/* Links */ +#app a[class*='primary'] { + color: #d4d4d8; /* text-neutral-300 */ + font-weight: 800; +} + +/* Status/error messages - matches text-yellow-500 text-center mt-4 */ +#app div[class*='status'] { + color: #eab308; /* text-yellow-500 */ + text-align: center; + margin-top: 16px; + font-size: 0.875rem; +} + +/* Hide the clear (suffix) button on identifier input */ +#app form div[class*='inputField'] [class$='suffix'] { + display: none !important; +} + +/* Hide native clear buttons in various browsers */ +#app form div[class*='inputField'] input::-ms-clear, +#app form div[class*='inputField'] input::-ms-reveal { + display: none; + width: 0; + height: 0; +} + +#app form div[class*='inputField'] input[type='search']::-webkit-search-cancel-button, +#app form div[class*='inputField'] input::-webkit-clear-button { + -webkit-appearance: none; + appearance: none; + display: none; +} \ No newline at end of file