From 33f6b320139c88dc886ed4cf81fe45c72092e375 Mon Sep 17 00:00:00 2001 From: Eiman Eltigani Date: Tue, 2 Dec 2025 07:55:33 -0700 Subject: [PATCH] update design for both react sites --- .../src/SecureSignalsApp.tsx | 268 +++++++--- .../react-client-side/src/styles/app.css | 455 +++++++++++++--- .../react-client-side/src/ClientSideApp.tsx | 251 ++++++--- .../react-client-side/src/styles/app.css | 486 ++++++++++++++---- 4 files changed, 1126 insertions(+), 334 deletions(-) diff --git a/web-integrations/google-secure-signals/react-client-side/src/SecureSignalsApp.tsx b/web-integrations/google-secure-signals/react-client-side/src/SecureSignalsApp.tsx index 9ae85364..01633be7 100644 --- a/web-integrations/google-secure-signals/react-client-side/src/SecureSignalsApp.tsx +++ b/web-integrations/google-secure-signals/react-client-side/src/SecureSignalsApp.tsx @@ -82,7 +82,7 @@ const SecureSignalsApp = () => { const onIdentityUpdated = useCallback( (eventType, payload) => { - console.log(`${IDENTITY_NAME} Callback`, payload); + console.log(`${IDENTITY_NAME} Callback`, payload); updateElements(payload); }, [updateElements] @@ -307,119 +307,241 @@ const SecureSignalsApp = () => { }; return ( -
-

- React Client-Side {IDENTITY_NAME} SDK Integration Example with Google Secure Signals -

-

- This example demonstrates how a content publisher can follow the{' '} - - Client-Side Integration Guide for JavaScript - {' '} - to implement {IDENTITY_NAME} integration and generate {IDENTITY_NAME} tokens. Secure Signals is updated when the - page is reloaded. Reload the page in order to update Secure Signals in local storage. -

- -
-
- -
+
+
+

+ React Client-Side {IDENTITY_NAME} SDK Integration Example with Google Secure Signals +

+

+ This example demonstrates how a content publisher can integrate {IDENTITY_NAME} with Google Secure Signals using client-side token generation with React, where the SDK generates tokens directly in the browser. For documentation, see the{' '} + + Client-Side Integration Guide for JavaScript + {' '} + and{' '} + + Google Ad Manager Secure Signals Integration Guide + . +

+ +
+
+ +
+
+
- -
-
- - - - - - +

{IDENTITY_NAME} Integration Status

+
{IDENTITY_NAME} Status
- + - + - + - + + + + + - + - +
Ready for Targeted Advertising: +
+ Ready for Targeted Advertising: +
+ ? +
+ Indicates whether a valid {IDENTITY_NAME} token is present and can be passed to Google Secure Signals for targeted advertising. +
+
+
+
{targetedAdvertisingReady ? 'yes' : 'no'}
{IDENTITY_NAME} Advertising Token: +
+ Advertising Token: +
+ ? +
+ The encrypted {IDENTITY_NAME} token passed to Google Secure Signals for advertising. It is automatically refreshed by the SDK in the background when expired. +
+
+
+
{advertisingToken}
Is {IDENTITY_NAME} Login Required? +
+ Is Login Required? +
+ ? +
+ Indicates whether a new {IDENTITY_NAME} token needs to be generated. Returns "yes" when no valid identity exists or the current identity has expired. +
+
+
+
{loginRequired ? 'yes' : 'no'}
{IDENTITY_NAME} Identity Callback State: +
+ Has opted out? +
+ ? +
+ Shows whether the user has exercised opt-out, in which case no advertising token may be generated or used. +
+
+
+
+
{isOptedOut ? 'yes' : 'no'}
+
+
+ Identity Callback State: +
+ ? +
+ The complete identity object returned by the SDK. Contains the full {IDENTITY_NAME} identity data including refresh tokens and metadata. +
+
+
+
{identityState}
Secure Signals Loaded? +
+ Secure Signals Loaded? +
+ ? +
+ Indicates whether Google Secure Signals has successfully loaded and cached the {IDENTITY_NAME} token. +
+
+
+
{secureSignalsLoaded ? 'yes' : 'no'}
Secure Signals Value: +
+ Secure Signals Value: +
+ ? +
+ The {IDENTITY_NAME} data stored by Google Secure Signals in local storage for use in ad requests. +
+
+
+
{secureSignalsValue}
-
- {isOptedOut ? ( - <> -
-

The email address you entered has opted out of {IDENTITY_NAME}.

+ {isOptedOut ? ( + <> +
+

The email address you entered has opted out of {IDENTITY_NAME}.

+
+
+ +
+ + ) : !isLoggedIn ? ( +
+
+ + +
-
- -
- - ) : !isLoggedIn ? ( -
-
- -
-
-
+ )} +
+ +
); }; diff --git a/web-integrations/google-secure-signals/react-client-side/src/styles/app.css b/web-integrations/google-secure-signals/react-client-side/src/styles/app.css index 759bf12b..3146620a 100644 --- a/web-integrations/google-secure-signals/react-client-side/src/styles/app.css +++ b/web-integrations/google-secure-signals/react-client-side/src/styles/app.css @@ -1,113 +1,406 @@ +/* Color Variables */ +:root { + /* Brand Colors */ + --primary-orange: #FF6B35; + --primary-dark: #2D3748; + --accent-teal: #0D9488; + --accent-yellow: #FBBF24; + + /* Text Colors */ + --text-dark: #1A202C; + --text-gray: #718096; + + /* Background Colors */ + --bg-white: #FFFFFF; + --bg-light: #F7FAFC; + --sidebar-bg: #FFF7ED; + + /* Border Colors */ + --border-color: #E2E8F0; + + /* Button Colors */ + --button-navy: rgba(2, 10, 64, 1); + --button-navy-hover: rgba(2, 10, 64, 0.9); + + /* Link Colors */ + --link-color: #06B6D4; + --link-hover: #06B6D4; + + /* Tooltip Colors */ + --tooltip-bg: #1F2937; + --tooltip-trigger: #3B82F6; + --tooltip-trigger-hover: #2563EB; + + /* Shadows */ + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + body { - padding: 50px; - font: 14px 'Lucida Grande', Helvetica, Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif; + background: var(--bg-light); + color: var(--text-dark); + line-height: 1.6; + padding: 2rem; +} + +/* Two Column Layout */ +.page-wrapper { + display: flex; + gap: 2rem; + max-width: 1400px; + margin: 0 auto; +} + +/* Main Content Area (75%) */ +.main-content { + flex: 3; + background: var(--bg-white); + border-radius: 12px; + padding: 2.5rem; + box-shadow: var(--shadow-md); +} + +/* Sidebar (25%) */ +.sidebar { + flex: 1; + background: var(--sidebar-bg); + border-radius: 12px; + padding: 2rem; + box-shadow: var(--shadow); + border-left: 4px solid var(--primary-orange); + position: sticky; + top: 2rem; + height: fit-content; + max-height: calc(100vh - 4rem); + overflow-y: auto; } - a { - color: #00b7ff; +.sidebar h3 { + color: var(--primary-dark); + font-size: 1.1rem; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--primary-orange); } - .button { - border-style: none; - cursor: pointer; - align-items: center; - height: 40px; - width: 401px; - text-align: center; - position: absolute; - letter-spacing: 0.28px; - box-sizing: border-box; - color: white; - font-family: 'Raleway', Helvetica, Arial, serif; - font-size: 14px; - font-style: normal; - font-weight: 700; - text-transform: none; - text-indent: 0; - text-shadow: none; +.sidebar .section { + margin-bottom: 1.5rem; +} + +.sidebar .section h4 { + color: var(--accent-teal); + font-size: 0.9rem; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.sidebar ul { + margin-left: 1.25rem; + font-size: 0.875rem; + color: var(--text-gray); +} + +.sidebar li { + margin-bottom: 0.5rem; + line-height: 1.6; +} + +.sidebar .note { + background: rgba(255, 107, 53, 0.1); + padding: 0.75rem; + border-radius: 6px; + font-size: 0.85rem; + color: var(--text-dark); + margin-top: 1rem; +} + +.sidebar .note strong { + color: var(--primary-orange); +} + +/* Header */ +h1 { + font-size: 2rem; + font-weight: 800; + color: var(--primary-dark); + margin-bottom: 0.75rem; + line-height: 1.3; +} + +h2 { + font-size: 1.5rem; + font-weight: 700; + color: var(--primary-dark); + margin: 2rem 0 1rem 0; + padding-bottom: 0.5rem; + border-bottom: 3px solid var(--primary-orange); +} + +p { + font-size: 0.95rem; + color: var(--text-gray); + margin-bottom: 2rem; + line-height: 1.8; +} + +a { + color: var(--link-color); + text-decoration: underline; + font-weight: 500; + transition: opacity 0.2s ease; +} + +a:hover { + opacity: 0.8; +} + +/* State Table */ +#uid_state { + width: 100%; + border-collapse: collapse; + margin: 2rem 0; + font-size: 0.875rem; + border: 1px solid var(--border-color); + border-radius: 8px; + overflow: hidden; +} + +#uid_state tr { + border-bottom: 1px solid var(--border-color); +} + +#uid_state tr:nth-child(even) { + background-color: var(--bg-light); +} + +#uid_state tr:last-child { + border-bottom: none; +} + +#uid_state td { + padding: 1rem; + vertical-align: top; +} + +.label { + font-weight: 600; + color: var(--text-dark); + white-space: nowrap; + padding-right: 2rem; + width: 15em; +} + +.value { + color: var(--text-gray); + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; +} + +.value pre { + white-space: pre-wrap; + word-break: break-all; margin: 0; - padding: 1px 6px; - background-color: rgba(2, 10, 64); - border-image: initial; } +/* Forms */ .form { - margin-top: 40px; + margin-top: 2rem; } .email_prompt { + display: flex; + gap: 0; + max-width: 600px; + box-shadow: var(--shadow); + border-radius: 8px; + overflow: hidden; +} + +#email { + flex: 1; + padding: 0.875rem 1.25rem; + border: 2px solid var(--border-color); + border-right: none; + font-size: 0.95rem; + color: var(--text-dark); + outline: none; + transition: border-color 0.2s ease; +} + +#email:focus { + border-color: var(--primary-orange); +} + +#email::placeholder { + color: var(--text-gray); +} + +/* Buttons */ +.button { + padding: 0.875rem 2rem; + background: var(--button-navy); + color: white; + border: none; + font-size: 0.95rem; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + box-shadow: var(--shadow); +} + +.button:hover { + background: var(--button-navy-hover); + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(2, 10, 64, 0.3); +} + +.button:active { + transform: translateY(0); +} + +/* Full width button for logout */ +#logout_form .button { + width: 100%; + max-width: 600px; + border-radius: 8px; +} + +/* Success Message */ +.message { + background: linear-gradient(135deg, rgba(34, 197, 94, 0.1) 0%, rgba(34, 197, 94, 0.05) 100%); + border-left: 4px solid #22C55E; + color: #15803D; + padding: 1.25rem; + margin: 1.5rem 0; + font-size: 0.95rem; + font-weight: 600; + border-radius: 6px; +} + +.product-tables { + display: flex; + flex-direction: row; + gap: 2rem; + margin-top: 2rem; +} + +/* Tooltip Styles - Matching Self-Serve Portal */ +.tooltip-wrapper { + display: inline-flex; align-items: center; - align-self: center; - background-color: white; - border: 1px solid rgba(2, 10, 64); - border-radius: 2px; - box-sizing: border-box; + gap: 0.5rem; +} + +.tooltip { + position: relative; display: inline-flex; - flex-direction: row; + align-items: center; + cursor: help; +} + +.tooltip-trigger { + width: 16px; + height: 16px; + border-radius: 50%; + background-color: var(--tooltip-trigger); + color: white; + border: none; + font-size: 0.7rem; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + cursor: help; + transition: all 0.2s ease; flex-shrink: 0; - height: 40px; - justify-content: flex-start; - margin-right: 1px; - margin-bottom: 20px; - min-width: 399px; - padding: 0 16px; +} + +.tooltip-trigger:hover { + background-color: var(--tooltip-trigger-hover); + transform: scale(1.05); +} + +.tooltip-content { + visibility: hidden; + opacity: 0; + position: absolute; + bottom: calc(100% + 8px); /* Position above with gap */ + left: 50%; + transform: translateX(-50%); + background-color: var(--tooltip-bg); + color: white; + padding: 10px; + border-radius: 4px; + font-size: 0.75rem; + line-height: 1.125; + min-width: 300px; + max-width: 570px; + white-space: normal; + z-index: 10000; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + transition: opacity 0.2s ease, visibility 0.2s ease; + font-weight: 400; + pointer-events: none; +} + +.tooltip-content::after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 6px; + border-style: solid; + border-color: #1F2937 transparent transparent transparent; +} + +.tooltip:hover .tooltip-content { + visibility: visible; + opacity: 1; +} + +/* Ensure table doesn't clip tooltips */ +#uid_state { position: relative; - width: auto; + overflow: visible; } - .email_prompt input { - border-style: none; +@media (max-width: 1024px) { + .page-wrapper { + flex-direction: column; } - table { - width: 100%; + .sidebar { + position: static; + max-height: none; } - #email { - background-color: white; - flex-shrink: 0; - height: auto; - letter-spacing: 0.12px; - line-height: 16px; - min-height: 16px; - position: relative; - text-align: left; - white-space: nowrap; - width: 351px; - color: rgba(2, 10, 64, 1); - font-family: 'Raleway', Helvetica, Arial, serif; - font-size: 12px; - font-style: normal; - font-weight: 500; - padding: 1px 2px; - outline: none; + body { + padding: 1rem; } - h1 { - padding-bottom: 20px; + .main-content { + padding: 1.5rem; } - .label { - white-space: nowrap; - padding-right: 20px; - width: 20em; - } - tr { - margin-top: 10px; + .email_prompt { + flex-direction: column; } - pre { - white-space: pre-wrap; - word-break: break-all; + #email { + border-right: 2px solid var(--border-color); + border-bottom: none; } - .message { - color: green; - padding: 20px; - margin-left: -22px; - font-size: 16px; - font-weight: 500; - border: 2px solid green; - border-radius: 5px; + .button { + width: 100%; + } } \ No newline at end of file diff --git a/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx b/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx index 539e6a8a..fddd4bee 100644 --- a/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx +++ b/web-integrations/javascript-sdk/react-client-side/src/ClientSideApp.tsx @@ -24,7 +24,6 @@ const ClientSideApp = () => { const [advertisingToken, setAdvertisingToken] = useState('undefined'); const [loginRequired, setLoginRequired] = useState('yes'); const [hasOptedOut, setHasOptedOut] = useState('no'); - const [updateCounter, setUpdateCounter] = useState(0); const [identityState, setIdentityState] = useState(''); const [showLoginForm, setShowLoginForm] = useState(true); @@ -49,12 +48,6 @@ const ClientSideApp = () => { // Callback for identity updates const onIdentityUpdated = useCallback( (eventType: string, payload: any) => { - if ( - payload?.identity && - (eventType === 'InitCompleted' || eventType === 'IdentityUpdated') - ) { - setUpdateCounter((prev) => prev + 1); - } updateGuiElements(payload); }, [updateGuiElements] @@ -91,89 +84,181 @@ const ClientSideApp = () => { }; return ( -
-

React Client-Side {IDENTITY_NAME} Integration Example using JavaScript SDK

-

- This example demonstrates how a content publisher can follow the{' '} - - Client-Side Integration Guide for JavaScript - {' '} - to implement {IDENTITY_NAME} integration and generate {IDENTITY_NAME} tokens using React.{' '} - Note: This is a test-only integration environment—not for production - use. It does not perform real user authentication or generate production-level tokens. Do not - use real user data on this page. -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Ready for Targeted Advertising: -
{targetedAdvertisingReady}
-
Advertising Token: -
{advertisingToken}
-
Is Login Required? -
{loginRequired}
-
Has opted out? -
{hasOptedOut}
-
Identity Updated Counter: -
{updateCounter}
-
Identity Callback State: -
{identityState}
-
- - {showLoginForm ? ( -
-
- setEmail(e.target.value)} - /> +
+
+

React Client-Side {IDENTITY_NAME} Integration Example using JavaScript SDK

+

+ This example demonstrates how a content publisher can integrate {IDENTITY_NAME} using client-side token generation with React, where the SDK generates tokens directly in the browser using public credentials. For documentation, see the{' '} + + Client-Side Integration Guide for JavaScript + . +

+ +

{IDENTITY_NAME} Integration Status

+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ Ready for Targeted Advertising: +
+ ? +
+ Indicates whether a valid {IDENTITY_NAME} token is present and can be used for personalized ad targeting. +
+
+
+
+
{targetedAdvertisingReady}
+
+
+ Advertising Token: +
+ ? +
+ The encrypted {IDENTITY_NAME} token that is passed to ad systems without exposing raw user identity. It is automatically refreshed by the SDK in the background when expired. +
+
+
+
+
{advertisingToken}
+
+
+ Is Login Required? +
+ ? +
+ Indicates whether a new {IDENTITY_NAME} token needs to be generated. Returns "yes" when no valid identity exists or the current identity has expired. +
+
+
+
+
{loginRequired}
+
+
+ Has opted out? +
+ ? +
+ Shows whether the user has exercised opt-out, in which case no advertising token may be generated or used. +
+
+
+
+
{hasOptedOut}
+
+
+ Identity Callback State: +
+ ? +
+ The complete identity object returned by the SDK. Contains the full {IDENTITY_NAME} identity data including refresh tokens and metadata. +
+
+
+
+
{identityState}
+
+ + {showLoginForm ? ( +
+
+ setEmail(e.target.value)} + /> + +
-
-
+ )} +
+ +
); }; export default ClientSideApp; - diff --git a/web-integrations/javascript-sdk/react-client-side/src/styles/app.css b/web-integrations/javascript-sdk/react-client-side/src/styles/app.css index 759bf12b..3ef2298b 100644 --- a/web-integrations/javascript-sdk/react-client-side/src/styles/app.css +++ b/web-integrations/javascript-sdk/react-client-side/src/styles/app.css @@ -1,113 +1,405 @@ +/* Color Variables */ +:root { + /* Brand Colors */ + --primary-orange: #FF6B35; + --primary-dark: #2D3748; + --accent-teal: #0D9488; + --accent-yellow: #FBBF24; + + /* Text Colors */ + --text-dark: #1A202C; + --text-gray: #718096; + + /* Background Colors */ + --bg-white: #FFFFFF; + --bg-light: #F7FAFC; + --sidebar-bg: #FFF7ED; + + /* Border Colors */ + --border-color: #E2E8F0; + + /* Button Colors */ + --button-navy: rgba(2, 10, 64, 1); + --button-navy-hover: rgba(2, 10, 64, 0.9); + + /* Link Colors */ + --link-color: #06B6D4; + --link-hover: #06B6D4; + + /* Tooltip Colors */ + --tooltip-bg: #1F2937; + --tooltip-trigger: #3B82F6; + --tooltip-trigger-hover: #2563EB; + + /* Shadows */ + --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + body { - padding: 50px; - font: 14px 'Lucida Grande', Helvetica, Arial, sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif; + background: var(--bg-light); + color: var(--text-dark); + line-height: 1.6; + padding: 2rem; +} + +/* Two Column Layout */ +.page-wrapper { + display: flex; + gap: 2rem; + max-width: 1400px; + margin: 0 auto; +} + +/* Main Content Area (75%) */ +.main-content { + flex: 3; + background: var(--bg-white); + border-radius: 12px; + padding: 2.5rem; + box-shadow: var(--shadow-md); +} + +/* Sidebar (25%) */ +.sidebar { + flex: 1; + background: var(--sidebar-bg); + border-radius: 12px; + padding: 2rem; + box-shadow: var(--shadow); + border-left: 4px solid var(--primary-orange); + position: sticky; + top: 2rem; + height: fit-content; + max-height: calc(100vh - 4rem); + overflow-y: auto; +} + +.sidebar h3 { + color: var(--primary-dark); + font-size: 1.1rem; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 2px solid var(--primary-orange); +} + +.sidebar .section { + margin-bottom: 1.5rem; +} + +.sidebar .section h4 { + color: var(--accent-teal); + font-size: 0.9rem; + font-weight: 600; + margin-bottom: 0.5rem; +} + +.sidebar ul { + margin-left: 1.25rem; + font-size: 0.875rem; + color: var(--text-gray); +} + +.sidebar li { + margin-bottom: 0.5rem; + line-height: 1.6; +} + +.sidebar .note { + background: rgba(255, 107, 53, 0.1); + padding: 0.75rem; + border-radius: 6px; + font-size: 0.85rem; + color: var(--text-dark); + margin-top: 1rem; +} + +.sidebar .note strong { + color: var(--primary-orange); +} + +/* Header */ +h1 { + font-size: 2rem; + font-weight: 800; + color: var(--primary-dark); + margin-bottom: 0.75rem; + line-height: 1.3; +} + +h2 { + font-size: 1.5rem; + font-weight: 700; + color: var(--primary-dark); + margin: 2rem 0 1rem 0; + padding-bottom: 0.5rem; + border-bottom: 3px solid var(--primary-orange); +} + +p { + font-size: 0.95rem; + color: var(--text-gray); + margin-bottom: 2rem; + line-height: 1.8; +} + +a { + color: var(--link-color); + text-decoration: underline; + font-weight: 500; + transition: opacity 0.2s ease; +} + +a:hover { + opacity: 0.8; +} + +/* State Table */ +#uid_state { + width: 100%; + border-collapse: collapse; + margin: 2rem 0; + font-size: 0.875rem; + border: 1px solid var(--border-color); + border-radius: 8px; + overflow: hidden; +} + +#uid_state tr { + border-bottom: 1px solid var(--border-color); +} + +#uid_state tr:nth-child(even) { + background-color: var(--bg-light); +} + +#uid_state tr:last-child { + border-bottom: none; +} + +#uid_state td { + padding: 1rem; + vertical-align: top; +} + +.label { + font-weight: 600; + color: var(--text-dark); + white-space: nowrap; + padding-right: 2rem; + width: 15em; +} + +.value { + color: var(--text-gray); + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; +} + +.value pre { + white-space: pre-wrap; + word-break: break-all; + margin: 0; +} + +/* Forms */ +.form { + margin-top: 2rem; +} + +.email_prompt { + display: flex; + gap: 0; + max-width: 600px; + box-shadow: var(--shadow); + border-radius: 8px; + overflow: hidden; +} + +#email { + flex: 1; + padding: 0.875rem 1.25rem; + border: 2px solid var(--border-color); + border-right: none; + font-size: 0.95rem; + color: var(--text-dark); + outline: none; + transition: border-color 0.2s ease; +} + +#email:focus { + border-color: var(--primary-orange); +} + +#email::placeholder { + color: var(--text-gray); +} + +/* Buttons */ +.button { + padding: 0.875rem 2rem; + background: var(--button-navy); + color: white; + border: none; + font-size: 0.95rem; + font-weight: 700; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + box-shadow: var(--shadow); +} + +.button:hover { + background: var(--button-navy-hover); + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(2, 10, 64, 0.3); +} + +.button:active { + transform: translateY(0); +} + +/* Full width button for logout */ +#logout_form .button { + width: 100%; + max-width: 600px; + border-radius: 8px; +} + +/* Success Message */ +.message { + background: linear-gradient(135deg, rgba(34, 197, 94, 0.1) 0%, rgba(34, 197, 94, 0.05) 100%); + border-left: 4px solid #22C55E; + color: #15803D; + padding: 1.25rem; + margin: 1.5rem 0; + font-size: 0.95rem; + font-weight: 600; + border-radius: 6px; +} + +.product-tables { + display: flex; + flex-direction: row; + gap: 2rem; + margin-top: 2rem; +} + +/* Tooltip Styles - Matching Self-Serve Portal */ +.tooltip-wrapper { + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.tooltip { + position: relative; + display: inline-flex; + align-items: center; + cursor: help; +} + +.tooltip-trigger { + width: 16px; + height: 16px; + border-radius: 50%; + background-color: var(--tooltip-trigger); + color: white; + border: none; + font-size: 0.7rem; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + cursor: help; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.tooltip-trigger:hover { + background-color: var(--tooltip-trigger-hover); + transform: scale(1.05); +} + +.tooltip-content { + visibility: hidden; + opacity: 0; + position: absolute; + bottom: calc(100% + 8px); /* Position above with gap */ + left: 50%; + transform: translateX(-50%); + background-color: var(--tooltip-bg); + color: white; + padding: 10px; + border-radius: 4px; + font-size: 0.75rem; + line-height: 1.125; + min-width: 300px; + max-width: 570px; + white-space: normal; + z-index: 10000; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + transition: opacity 0.2s ease, visibility 0.2s ease; + font-weight: 400; + pointer-events: none; +} + +.tooltip-content::after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border-width: 6px; + border-style: solid; + border-color: #1F2937 transparent transparent transparent; +} + +.tooltip:hover .tooltip-content { + visibility: visible; + opacity: 1; +} + +/* Ensure table doesn't clip tooltips */ +#uid_state { + position: relative; + overflow: visible; +} + +@media (max-width: 1024px) { + .page-wrapper { + flex-direction: column; } - a { - color: #00b7ff; + .sidebar { + position: static; + max-height: none; } - .button { - border-style: none; - cursor: pointer; - align-items: center; - height: 40px; - width: 401px; - text-align: center; - position: absolute; - letter-spacing: 0.28px; - box-sizing: border-box; - color: white; - font-family: 'Raleway', Helvetica, Arial, serif; - font-size: 14px; - font-style: normal; - font-weight: 700; - text-transform: none; - text-indent: 0; - text-shadow: none; - margin: 0; - padding: 1px 6px; - background-color: rgba(2, 10, 64); - border-image: initial; + body { + padding: 1rem; } - .form { - margin-top: 40px; + .main-content { + padding: 1.5rem; } .email_prompt { - align-items: center; - align-self: center; - background-color: white; - border: 1px solid rgba(2, 10, 64); - border-radius: 2px; - box-sizing: border-box; - display: inline-flex; - flex-direction: row; - flex-shrink: 0; - height: 40px; - justify-content: flex-start; - margin-right: 1px; - margin-bottom: 20px; - min-width: 399px; - padding: 0 16px; - position: relative; - width: auto; - } - - .email_prompt input { - border-style: none; - } - - table { - width: 100%; + flex-direction: column; } #email { - background-color: white; - flex-shrink: 0; - height: auto; - letter-spacing: 0.12px; - line-height: 16px; - min-height: 16px; - position: relative; - text-align: left; - white-space: nowrap; - width: 351px; - color: rgba(2, 10, 64, 1); - font-family: 'Raleway', Helvetica, Arial, serif; - font-size: 12px; - font-style: normal; - font-weight: 500; - padding: 1px 2px; - outline: none; - } - - h1 { - padding-bottom: 20px; + border-right: 2px solid var(--border-color); + border-bottom: none; } - .label { - white-space: nowrap; - padding-right: 20px; - width: 20em; - } - tr { - margin-top: 10px; - } - - pre { - white-space: pre-wrap; - word-break: break-all; - } - - .message { - color: green; - padding: 20px; - margin-left: -22px; - font-size: 16px; - font-weight: 500; - border: 2px solid green; - border-radius: 5px; + .button { + width: 100%; } - \ No newline at end of file +}