feat: Actualizar CDC Brief con branding GLM y ajustes UX finales

This commit is contained in:
2026-06-27 11:46:57 -04:00
parent 82d59a3313
commit b04abe7515
16 changed files with 941 additions and 1066 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -10
View File
@@ -1,10 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="GLM"><rect width="64" height="64" rx="14" fill="#FFFFFF"/><rect x="7" y="20" width="50" height="24" rx="4" fill="#4F758B"/><path d="M29 20h13v17c0 3-2.5 5.5-5.5 5.5H29V20Z" fill="#6CC24A"/><path d="M28 20h4v24h-4z" fill="#6CC24A"/><path d="M24 28h4v16h-4z" fill="#6CC24A"/></svg>
<defs>
<linearGradient id="g" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#6D7CFF"/>
<stop offset="1" stop-color="#2FE6FF"/>
</linearGradient>
</defs>
<rect x="6" y="6" width="52" height="52" rx="16" fill="url(#g)"/>
<path d="M34.6 15.5L20.8 34.5H30l-0.9 14 14.1-19.2h-9.1l0.5-13.8Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 454 B

After

Width:  |  Height:  |  Size: 363 B

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+9 -4
View File
@@ -1,12 +1,17 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<link rel="icon" type="image/svg+xml" href="/brief/favicon.svg" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="icon" type="image/png" href="glm-logo.png?v=5" />
<link rel="shortcut icon" type="image/png" href="glm-logo.png?v=5" />
<link rel="apple-touch-icon" href="glm-logo.png?v=5" />
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CDC Brief GLM</title> <title>CDC Brief | GLM</title>
<script type="module" crossorigin src="/brief/assets/index-BjRIMN2B.js"></script> <script type="module" crossorigin src="/brief/assets/index-DT0rQidg.js"></script>
<link rel="stylesheet" crossorigin href="/brief/assets/index-D-Qs_b2T.css"> <link rel="stylesheet" crossorigin href="/brief/assets/index-B-WJR2cH.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+7 -2
View File
@@ -1,10 +1,15 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="icon" type="image/png" href="glm-logo.png?v=5" />
<link rel="shortcut icon" type="image/png" href="glm-logo.png?v=5" />
<link rel="apple-touch-icon" href="glm-logo.png?v=5" />
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CDC Brief GLM</title> <title>CDC Brief | GLM</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+1 -10
View File
@@ -1,10 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="GLM"><rect width="64" height="64" rx="14" fill="#FFFFFF"/><rect x="7" y="20" width="50" height="24" rx="4" fill="#4F758B"/><path d="M29 20h13v17c0 3-2.5 5.5-5.5 5.5H29V20Z" fill="#6CC24A"/><path d="M28 20h4v24h-4z" fill="#6CC24A"/><path d="M24 28h4v16h-4z" fill="#6CC24A"/></svg>
<defs>
<linearGradient id="g" x1="8" y1="8" x2="56" y2="56" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#6D7CFF"/>
<stop offset="1" stop-color="#2FE6FF"/>
</linearGradient>
</defs>
<rect x="6" y="6" width="52" height="52" rx="16" fill="url(#g)"/>
<path d="M34.6 15.5L20.8 34.5H30l-0.9 14 14.1-19.2h-9.1l0.5-13.8Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 454 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+19 -103
View File
@@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'motion/react'; import { motion, AnimatePresence } from 'motion/react';
import { FileText, Loader2, ShieldCheck, Sparkles, Workflow } from 'lucide-react'; import { Loader2 } from 'lucide-react';
import { onAuthStateChanged, signInWithPopup, signOut, User } from 'firebase/auth'; import { onAuthStateChanged, signInWithPopup, signOut, User } from 'firebase/auth';
import { Header } from './components/Header'; import { Header } from './components/Header';
@@ -209,7 +209,7 @@ Verifica que la URL esté correcta en el entorno y que el endpoint esté activo
if (authLoading) { if (authLoading) {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-[#060816] text-zinc-200"> <div className="flex min-h-screen items-center justify-center bg-[#F7FAF8] text-[#4F758B]">
Cargando acceso... Cargando acceso...
</div> </div>
); );
@@ -226,112 +226,30 @@ Verifica que la URL esté correcta en el entorno y que el endpoint esté activo
} }
return ( return (
<div className="relative flex min-h-screen flex-col overflow-x-hidden bg-[#060816] text-zinc-100 font-sans selection:bg-indigo-500/30"> <div className="glm-app relative flex min-h-screen flex-col overflow-x-hidden bg-[#F4F7F5] font-['Plus_Jakarta_Sans',sans-serif] text-[#4A4A4A] selection:bg-[#6CC24A]/20">
{/* Subtle mesh background */}
<div className="pointer-events-none absolute inset-0 overflow-hidden"> <div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute left-[-10%] top-[-15%] h-[34rem] w-[34rem] rounded-full bg-indigo-600/18 blur-3xl" /> <div className="absolute left-[-10%] top-[-10%] h-[32rem] w-[32rem] rounded-full bg-[#EEF6E8]/60 blur-[90px]" />
<div className="absolute right-[-8%] top-[8%] h-[30rem] w-[30rem] rounded-full bg-cyan-400/12 blur-3xl" /> <div className="absolute right-[-8%] top-[15%] h-[28rem] w-[28rem] rounded-full bg-[#D6E8F4]/60 blur-[90px]" />
<div className="absolute bottom-[-18%] left-[20%] h-[28rem] w-[28rem] rounded-full bg-fuchsia-500/10 blur-3xl" />
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-[size:42px_42px] opacity-[0.06]" />
</div> </div>
<Header isConnected={isConnected} onLogout={handleLogout} /> <Header isConnected={isConnected} onLogout={handleLogout} />
<main className="relative flex-1"> <main className="relative flex-1">
<div className="mx-auto flex w-full max-w-7xl flex-col gap-6 px-4 py-6 pb-8 sm:px-6 lg:px-8"> <div className="mx-auto w-full max-w-6xl px-4 py-6 pb-10 sm:px-6 lg:px-8 flex flex-col gap-5">
<section className="overflow-hidden rounded-[32px] border border-white/10 bg-white/5 shadow-[0_30px_80px_rgba(0,0,0,0.35)] backdrop-blur-xl">
<div className="grid gap-0 lg:grid-cols-[1.5fr_1fr]">
<div className="relative p-6 sm:p-7 lg:p-8">
<div className="absolute inset-0 bg-[radial-gradient(circle_at_top_left,rgba(99,102,241,0.22),transparent_38%),radial-gradient(circle_at_bottom_right,rgba(34,211,238,0.14),transparent_36%)]" />
<div className="relative">
<div className="mb-4 inline-flex items-center gap-2 rounded-full border border-indigo-400/25 bg-indigo-500/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.22em] text-indigo-200">
<Sparkles className="h-3.5 w-3.5" /> Portal inteligente de briefs
</div>
<h2 className="max-w-3xl text-3xl font-semibold tracking-tight text-white sm:text-4xl">
Carga el brief en un flujo claro, visual y mucho más profesional.
</h2>
<p className="mt-4 max-w-2xl text-sm leading-7 text-zinc-300 sm:text-base">
La interfaz guía al usuario paso por paso, reduce errores de carga y organiza audio, imágenes, archivos, piezas gráficas y referencias para construir un brief listo para revisión interna.
</p>
<div className="mt-6 flex flex-wrap gap-3">
{[
{ icon: ShieldCheck, label: 'Validaciones obligatorias' },
{ icon: Workflow, label: 'Proceso guiado por pasos' },
{ icon: FileText, label: 'Entrega estructurada al flujo' },
].map(({ icon: Icon, label }) => (
<div
key={label}
className="inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-zinc-200"
>
<Icon className="h-4 w-4 text-indigo-300" />
<span>{label}</span>
</div>
))}
</div>
</div>
</div>
<div className="border-t border-white/10 bg-black/20 p-6 sm:p-7 lg:border-l lg:border-t-0 lg:p-8">
<div className="rounded-[28px] border border-white/10 bg-black/25 p-5">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">
Resumen del sistema
</p>
<div className="mt-4 space-y-4">
{[
'La nota de voz, las imágenes y los PDFs sí se analizan automáticamente.',
'Word, Excel, PowerPoint y otros adjuntos se guardan en la carpeta del proyecto.',
'Los enlaces online se registran para alimentar el brief final.',
'El usuario no necesita usar el portal como chat libre: todo queda guiado.',
].map((item) => (
<div key={item} className="flex items-start gap-3">
<div className="mt-1 h-2.5 w-2.5 rounded-full bg-indigo-400 shadow-[0_0_18px_rgba(129,140,248,0.7)]" />
<p className="text-sm leading-6 text-zinc-300">{item}</p>
</div>
))}
</div>
</div>
</div>
</div>
</section>
<section className="rounded-[30px] border border-white/10 bg-black/20 p-4 shadow-[0_24px_60px_rgba(0,0,0,0.28)] backdrop-blur-xl sm:p-5">
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500">
Carga guiada
</p>
<h3 className="mt-1 text-lg font-semibold text-white sm:text-xl">
Completa el brief y envía la solicitud
</h3>
</div>
</div>
{/* Brief Input Section */}
<section className="overflow-hidden rounded-[28px] border border-white/60 bg-white/80 shadow-[0_8px_40px_rgba(79,117,139,0.10)] backdrop-blur-xl">
<InputArea <InputArea
onSendMessage={handleSendMessage} onSendMessage={handleSendMessage}
isProcessing={isProcessing} isProcessing={isProcessing}
/> />
</section> </section>
<section className="rounded-[30px] border border-white/10 bg-black/20 p-4 shadow-[0_24px_60px_rgba(0,0,0,0.28)] backdrop-blur-xl sm:p-5"> {/* Response Section — only show when there's content */}
<div className="mb-4 flex flex-wrap items-center justify-between gap-3"> {(messages.length > 0 || isProcessing) && (
<div> <section className="overflow-hidden rounded-[28px] border border-white/60 bg-white/80 p-5 shadow-[0_8px_40px_rgba(79,117,139,0.10)] backdrop-blur-xl sm:p-6">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-zinc-500"> <p className="mb-4 text-xs font-bold uppercase tracking-[0.22em] text-[#6B8FA3]">Respuesta del portal</p>
Respuesta del portal
</p>
<h3 className="mt-1 text-lg font-semibold text-white sm:text-xl">
Resultado de la solicitud
</h3>
</div>
</div>
<div className="min-h-[220px] rounded-[24px] border border-white/10 bg-white/[0.03] p-4 sm:p-5 lg:p-6">
{messages.length === 0 && !isProcessing && (
<div className="flex min-h-[150px] items-center justify-center rounded-[22px] border border-dashed border-white/10 bg-black/10 px-5 text-center text-sm leading-6 text-zinc-500">
Cuando envíes la solicitud, aquí aparecerá la respuesta del flujo y el enlace de Drive.
</div>
)}
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{messages.map((message) => ( {messages.map((message) => (
@@ -344,18 +262,16 @@ Verifica que la URL esté correcta en el entorno y que el endpoint esté activo
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9 }} exit={{ opacity: 0, scale: 0.9 }}
className="mb-4 flex w-fit items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3" className="flex w-fit items-center gap-3 rounded-2xl border border-[#D0D0D0]/60 bg-white px-4 py-3 shadow-sm"
> >
<Loader2 className="h-4 w-4 animate-spin text-indigo-400" /> <Loader2 className="h-4 w-4 animate-spin text-[#6CC24A]" />
<span className="text-sm font-medium text-zinc-300"> <span className="text-sm font-medium text-[#4A4A4A]">Procesando la solicitud...</span>
Procesando la solicitud...
</span>
</motion.div> </motion.div>
)} )}
<div ref={messagesEndRef} className="h-2" /> <div ref={messagesEndRef} className="h-1" />
</div>
</section> </section>
)}
</div> </div>
</main> </main>
</div> </div>
+27 -34
View File
@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { LogOut, Sparkles, Zap } from 'lucide-react'; import { LogOut } from 'lucide-react';
import { cn } from '../lib/utils'; import { cn } from '../lib/utils';
interface HeaderProps { interface HeaderProps {
@@ -7,55 +7,48 @@ interface HeaderProps {
onLogout: () => void; onLogout: () => void;
} }
const logoSrc = `${import.meta.env.BASE_URL}glm-logo.png`;
export const Header: React.FC<HeaderProps> = ({ isConnected, onLogout }) => { export const Header: React.FC<HeaderProps> = ({ isConnected, onLogout }) => {
return ( return (
<header className="relative z-50 border-b border-white/10 bg-black/25 px-4 py-4 backdrop-blur-2xl sm:px-6 lg:px-8"> <header className="sticky top-0 z-50 border-b border-[#D0D0D0]/60 bg-white/90 backdrop-blur-2xl shadow-[0_4px_24px_rgba(79,117,139,0.07)]">
<div className="mx-auto flex w-full max-w-7xl items-center justify-between gap-4"> <div className="mx-auto flex w-full max-w-7xl items-center justify-between gap-4 px-5 py-3 sm:px-8">
<div className="flex min-w-0 items-center gap-3 sm:gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-[linear-gradient(135deg,#6366f1_0%,#2563eb_50%,#06b6d4_100%)] shadow-[0_18px_40px_rgba(79,70,229,0.38)]">
<Zap className="h-5 w-5 text-white" />
</div>
<div className="min-w-0"> {/* Left: Logo + Title */}
<div className="flex flex-wrap items-center gap-2"> <div className="flex items-center gap-4">
<h1 className="truncate text-lg font-semibold tracking-tight text-white sm:text-xl"> <div className="flex h-14 w-14 items-center justify-center overflow-hidden rounded-[18px] border border-slate-200/80 bg-white p-1.5 shadow-[0_4px_14px_rgba(79,117,139,0.10)]">
<img src={logoSrc} alt="GLM" className="h-full w-full object-contain" />
</div>
<div>
<h1 className="text-base font-bold tracking-tight text-[#4F758B] sm:text-lg">
Portal Brief CDC Portal Brief CDC
</h1> </h1>
<span className="hidden rounded-full border border-white/10 bg-white/5 px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.24em] text-zinc-400 sm:inline-flex"> <div className="mt-0.5 flex items-center gap-2">
Experiencia guiada <span className={cn(
</span> 'relative flex h-2 w-2 shrink-0'
</div> )}>
<div className="mt-1 flex flex-wrap items-center gap-2 sm:gap-3">
<div className="inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-2.5 py-1 text-xs text-zinc-300">
<span className="relative flex h-2.5 w-2.5">
{isConnected && ( {isConnected && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-70" /> <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#6CC24A] opacity-60" />
)} )}
<span <span className={cn(
className={cn( 'relative inline-flex h-2 w-2 rounded-full',
'relative inline-flex h-2.5 w-2.5 rounded-full', isConnected ? 'bg-[#6CC24A]' : 'bg-red-400'
isConnected ? 'bg-emerald-400' : 'bg-red-400' )} />
)} </span>
/> <span className="text-[11px] font-medium text-slate-400">
{isConnected ? 'Conectado' : 'Sin conexión'} · GomezLee Marketing
</span> </span>
{isConnected ? 'Conectado al flujo' : 'Sin conexión'}
</div>
<div className="hidden items-center gap-2 rounded-full border border-indigo-400/20 bg-indigo-500/10 px-2.5 py-1 text-xs text-indigo-200 sm:inline-flex">
<Sparkles className="h-3.5 w-3.5" />
Flujo validado
</div>
</div> </div>
</div> </div>
</div> </div>
{/* Right: Logout */}
<button <button
onClick={onLogout} onClick={onLogout}
className="inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/5 px-4 py-2.5 text-sm font-medium text-zinc-200 transition hover:border-white/20 hover:bg-white/10 hover:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/50" className="group inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-[#4F758B] shadow-sm transition-all hover:border-rose-200 hover:bg-rose-50 hover:text-rose-600 focus:outline-none focus:ring-2 focus:ring-[#6CC24A]/30"
aria-label="Cerrar sesión" aria-label="Cerrar sesión"
> >
<LogOut className="h-4 w-4" /> <LogOut className="h-4 w-4 transition-transform group-hover:translate-x-0.5" />
<span className="hidden sm:inline">Cerrar sesión</span> <span className="hidden sm:inline">Cerrar sesión</span>
</button> </button>
</div> </div>
File diff suppressed because it is too large Load Diff
+76 -35
View File
@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { motion } from 'motion/react'; import { motion } from 'motion/react';
import { Chrome, ShieldCheck, Sparkles } from 'lucide-react'; import { Loader2 } from 'lucide-react';
interface LoginScreenProps { interface LoginScreenProps {
onLogin: () => void; onLogin: () => void;
@@ -8,60 +8,101 @@ interface LoginScreenProps {
error?: string; error?: string;
} }
const logoSrc = `${import.meta.env.BASE_URL}glm-logo.png`;
export const LoginScreen: React.FC<LoginScreenProps> = ({ onLogin, isLoading, error }) => { export const LoginScreen: React.FC<LoginScreenProps> = ({ onLogin, isLoading, error }) => {
return ( return (
<div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-[#060816] px-4 text-zinc-100"> <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-[#F4F7F5] px-4 py-12 select-none">
{/* Premium Tech Grid Background */}
<div className="absolute inset-0 premium-grid-bg opacity-75 pointer-events-none" />
{/* Dynamic Animated Mesh Gradients */}
<div className="pointer-events-none absolute inset-0 overflow-hidden"> <div className="pointer-events-none absolute inset-0 overflow-hidden">
<div className="absolute left-[-10%] top-[-15%] h-[34rem] w-[34rem] rounded-full bg-indigo-600/18 blur-3xl" />
<div className="absolute right-[-8%] top-[8%] h-[30rem] w-[30rem] rounded-full bg-cyan-400/12 blur-3xl" />
<div className="absolute bottom-[-18%] left-[20%] h-[28rem] w-[28rem] rounded-full bg-fuchsia-500/10 blur-3xl" />
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.03)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.03)_1px,transparent_1px)] bg-[size:42px_42px] opacity-[0.06]" />
</div>
<motion.div <motion.div
initial={{ opacity: 0, y: 18, scale: 0.98 }} animate={{ x: [0, 45, -25, 0], y: [0, -60, 40, 0], scale: [1, 1.15, 0.95, 1] }}
animate={{ opacity: 1, y: 0, scale: 1 }} transition={{ duration: 22, repeat: Infinity, ease: 'easeInOut' }}
className="relative w-full max-w-xl rounded-[32px] border border-white/10 bg-white/5 p-8 shadow-[0_30px_80px_rgba(0,0,0,0.35)] backdrop-blur-xl" className="absolute left-[-8%] top-[-12%] h-[36rem] w-[36rem] rounded-full bg-[#EEF6E8]/70 blur-[90px]"
> />
<div className="mb-4 inline-flex items-center gap-2 rounded-full border border-indigo-400/25 bg-indigo-500/10 px-3 py-1 text-xs font-semibold uppercase tracking-[0.22em] text-indigo-200"> <motion.div
<Sparkles className="h-3.5 w-3.5" /> animate={{ x: [0, -35, 45, 0], y: [0, 50, -40, 0], scale: [1, 0.95, 1.1, 1] }}
Acceso corporativo transition={{ duration: 26, repeat: Infinity, ease: 'easeInOut' }}
className="absolute right-[-6%] top-[8%] h-[32rem] w-[32rem] rounded-full bg-[#D6E8F4]/70 blur-[100px]"
/>
<motion.div
animate={{ x: [0, 30, -30, 0], y: [0, 40, -40, 0], scale: [1, 1.05, 0.95, 1] }}
transition={{ duration: 18, repeat: Infinity, ease: 'easeInOut' }}
className="absolute bottom-[-15%] left-[20%] h-[30rem] w-[30rem] rounded-full bg-white/70 blur-[90px]"
/>
</div> </div>
<h1 className="text-3xl font-semibold tracking-tight text-white sm:text-4xl"> {/* Login Card */}
<motion.div
initial={{ opacity: 0, y: 32, scale: 0.97 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
transition={{ duration: 0.8, ease: [0.16, 1, 0.3, 1] }}
className="relative w-full max-w-[400px] overflow-hidden rounded-[32px] border border-white/50 bg-white/75 px-10 py-12 shadow-[0_32px_96px_rgba(79,117,139,0.14)] backdrop-blur-2xl font-['Plus_Jakarta_Sans',sans-serif]"
>
{/* Top gradient bar */}
<div className="absolute top-0 inset-x-0 h-[4px] bg-gradient-to-r from-[#6CC24A] via-[#5B7F95] to-[#4F758B]" />
{/* Centered content */}
<div className="flex flex-col items-center text-center">
{/* Logo */}
<div className="flex h-24 w-24 items-center justify-center overflow-hidden rounded-[24px] border border-slate-200/60 bg-white p-3 shadow-[0_8px_28px_rgba(79,117,139,0.12)]">
<img src={logoSrc} alt="GLM Logo" className="h-full w-full object-contain" />
</div>
{/* Title — full gradient, no black */}
<h1 className="mt-8 text-[2rem] font-bold tracking-tight leading-tight">
<span className="bg-gradient-to-r from-[#4F758B] via-[#5B7F95] to-[#6B8FA3] bg-clip-text text-transparent">
Portal Brief CDC Portal Brief CDC
</span>
</h1> </h1>
<p className="mt-4 text-sm leading-7 text-zinc-300 sm:text-base"> {/* Subtle tagline */}
Inicia sesión con tu cuenta corporativa de Google para acceder al portal. <p className="mt-2.5 text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">
GomezLee Marketing
</p> </p>
<div className="mt-6 rounded-2xl border border-white/10 bg-black/20 p-4"> {/* Error */}
<div className="flex items-start gap-3">
<ShieldCheck className="mt-0.5 h-5 w-5 text-indigo-300" />
<div>
<p className="text-sm font-semibold text-white">Acceso restringido</p>
<p className="mt-1 text-sm leading-6 text-zinc-400">
Solo usuarios autorizados de la empresa podrán continuar.
</p>
</div>
</div>
</div>
{error && ( {error && (
<div className="mt-4 rounded-2xl border border-red-500/20 bg-red-500/10 px-4 py-3 text-sm text-red-200"> <motion.div
{error} initial={{ opacity: 0, y: -10 }}
</div> animate={{ opacity: 1, y: 0 }}
className="mt-6 w-full rounded-xl border border-rose-100 bg-rose-50/50 px-4 py-3 text-xs leading-relaxed text-rose-600 flex items-start gap-2.5"
>
<div className="mt-1 h-1.5 w-1.5 shrink-0 rounded-full bg-rose-500 animate-pulse" />
<span>{error}</span>
</motion.div>
)} )}
{/* Google Login Button */}
<button <button
onClick={onLogin} onClick={onLogin}
disabled={isLoading} disabled={isLoading}
className="mt-6 inline-flex w-full items-center justify-center gap-3 rounded-2xl bg-[linear-gradient(135deg,#6366f1_0%,#2563eb_50%,#06b6d4_100%)] px-5 py-4 text-sm font-semibold text-white shadow-[0_18px_40px_rgba(79,70,229,0.38)] transition hover:opacity-95 disabled:cursor-not-allowed disabled:opacity-60" className="shimmer-btn group mt-10 relative flex w-full items-center justify-center gap-3 rounded-2xl bg-gradient-to-r from-[#4F758B] to-[#5B7F95] px-6 py-4 text-sm font-semibold text-white shadow-[0_12px_28px_rgba(79,117,139,0.2)] transition-all duration-300 hover:from-[#456A7F] hover:to-[#4F758B] hover:shadow-[0_16px_36px_rgba(79,117,139,0.3)] active:scale-[0.99] disabled:pointer-events-none disabled:opacity-50"
> >
<Chrome className="h-4 w-4" /> {isLoading ? (
<Loader2 className="h-5 w-5 animate-spin" />
) : (
<svg
className="h-5 w-5 shrink-0 transition-transform duration-300 group-hover:scale-110"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l2.85-2.22.81-.63z" fill="#FBBC05" />
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84c.87-2.6 3.3-4.52 6.16-4.52z" fill="#EA4335" />
</svg>
)}
<span className="tracking-wide">
{isLoading ? 'Abriendo Google...' : 'Continuar con Google'} {isLoading ? 'Abriendo Google...' : 'Continuar con Google'}
</span>
</button> </button>
</div>
</motion.div> </motion.div>
</div> </div>
); );
+36 -38
View File
@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { motion } from 'motion/react'; import { motion } from 'motion/react';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { AlertCircle, Check, CheckCheck, Copy, ExternalLink, FileText, Music, ShieldCheck, X } from 'lucide-react'; import { AlertCircle, Check, CheckCheck, Copy, ExternalLink, FileText, Music, Pause, Play, ShieldCheck, X } from 'lucide-react';
import { Message, Attachment } from '../types'; import { Message, Attachment } from '../types';
import { cn } from '../lib/utils'; import { cn } from '../lib/utils';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
@@ -24,47 +24,48 @@ const formatDuration = (seconds: number) => {
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
}; };
const getAttachmentLabel = (attachment: Attachment) => {
if (attachment.type === 'image') return 'Imagen de referencia';
if (attachment.type === 'audio') return 'Nota de voz';
if (attachment.category === 'campaign') return 'Campaña / gráfico';
if (attachment.type === 'document') return 'Documento';
return 'Archivo adjunto';
};
const AttachmentPreview = ({ attachment }: { attachment: Attachment }) => { const AttachmentPreview = ({ attachment }: { attachment: Attachment }) => {
const fileSizeKb = `${(attachment.file.size / 1024).toFixed(1)} KB`;
const label = getAttachmentLabel(attachment);
if (attachment.type === 'image') { if (attachment.type === 'image') {
return ( return (
<div className="group relative max-w-[320px] overflow-hidden rounded-2xl border border-white/10 bg-black/20"> <div className="glm-message-attachment flex w-full max-w-[320px] items-center gap-3 rounded-2xl border border-[#D0D0D0]/70 bg-white/95 px-3 py-3 text-[#4A4A4A] shadow-[0_8px_22px_rgba(79,117,139,0.10)]">
<div className="h-14 w-14 shrink-0 overflow-hidden rounded-xl border border-[#D0D0D0]/65 bg-[#F9FBFC]">
<img <img
src={attachment.url} src={attachment.url}
alt={attachment.file.name} alt={attachment.file.name}
className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.02]" className="h-full w-full object-cover"
/> />
<div className="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/80 to-transparent px-3 py-2 text-[11px] font-medium text-white/90"> </div>
<div className="truncate">{attachment.file.name}</div> <div className="min-w-0 flex-1">
<p className="truncate text-sm font-semibold text-[#2E4052]">{attachment.file.name}</p>
<p className="truncate text-xs font-medium text-[#6B8FA3]">{label}</p>
<p className="mt-0.5 text-[11px] text-[#8EA1B3]">{fileSizeKb}</p>
</div> </div>
</div> </div>
); );
} }
if (attachment.type === 'audio') { const Icon = attachment.type === 'audio' ? Music : FileText;
return (
<div className="flex w-full max-w-[420px] flex-col gap-3 rounded-2xl border border-white/10 bg-white/5 px-3 py-3">
<div className="flex items-center gap-3">
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-black/25">
<Music className="h-4 w-4 text-indigo-200" />
</div>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-white/90">{attachment.file.name}</p>
<p className="text-[11px] text-zinc-400">{(attachment.file.size / 1024).toFixed(1)} KB</p>
</div>
</div>
<audio controls src={attachment.url} className="h-9 w-full rounded-xl" />
</div>
);
}
return ( return (
<div className="flex w-full max-w-[360px] items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-3 py-3"> <div className="glm-message-attachment flex w-full max-w-[320px] items-center gap-3 rounded-2xl border border-[#D0D0D0]/70 bg-white/95 px-3 py-3 text-[#4A4A4A] shadow-[0_8px_22px_rgba(79,117,139,0.10)]">
<div className="flex h-11 w-11 items-center justify-center rounded-xl bg-black/25"> <div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-xl border border-[#D0D0D0]/60 bg-[#F7FAF8]">
<FileText className="h-4 w-4 text-indigo-200" /> <Icon className="h-5 w-5 text-[#4F758B]" />
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-white/90">{attachment.file.name}</p> <p className="truncate text-sm font-semibold text-[#2E4052]">{attachment.file.name}</p>
<p className="text-[11px] text-zinc-400">{(attachment.file.size / 1024).toFixed(1)} KB</p> <p className="truncate text-xs font-medium text-[#6B8FA3]">{label}</p>
<p className="mt-0.5 text-[11px] text-[#8EA1B3]">{fileSizeKb}</p>
</div> </div>
</div> </div>
); );
@@ -118,7 +119,7 @@ const DriveLinkCard = ({ url, onClose }: { url: string; onClose: () => void }) =
<button <button
type="button" type="button"
onClick={handleCopy} onClick={handleCopy}
className="inline-flex items-center gap-2 rounded-xl bg-white px-4 py-2.5 text-sm font-semibold text-slate-950 transition hover:bg-zinc-200" className="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white px-4 py-2.5 text-sm font-semibold text-[#4F758B] transition hover:bg-[#F8FBF6]"
> >
{copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />} {copied ? <Check className="h-4 w-4" /> : <Copy className="h-4 w-4" />}
{copied ? 'Copiado' : 'Copiar'} {copied ? 'Copiado' : 'Copiar'}
@@ -269,38 +270,35 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
)} )}
{message.audio && ( {message.audio && (
<div className="mb-4 rounded-2xl bg-black/20 p-3 pr-4"> <div className="glm-message-audio mb-4 rounded-2xl border border-[#D0D0D0]/70 bg-white/95 p-3 pr-4 shadow-[0_8px_22px_rgba(79,117,139,0.10)]">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
type="button" type="button"
onClick={togglePlay} onClick={togglePlay}
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-white/15 transition hover:bg-white/25" className="glm-message-audio-toggle flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[#4F758B] text-white shadow-[0_8px_18px_rgba(79,117,139,0.24)] transition hover:bg-[#5B7F95]"
aria-label={isPlaying ? 'Pausar audio' : 'Reproducir audio'} aria-label={isPlaying ? 'Pausar audio' : 'Reproducir audio'}
> >
{isPlaying ? ( {isPlaying ? (
<div className="flex h-3.5 w-3.5 gap-1"> <Pause className="h-5 w-5" fill="currentColor" />
<div className="h-full w-1 rounded-sm bg-white" />
<div className="h-full w-1 rounded-sm bg-white" />
</div>
) : ( ) : (
<div className="ml-0.5 h-0 w-0 border-b-[7px] border-l-[10px] border-t-[7px] border-b-transparent border-l-white border-t-transparent" /> <Play className="ml-0.5 h-5 w-5" fill="currentColor" />
)} )}
</button> </button>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="mb-1.5 flex items-center justify-between gap-3"> <div className="mb-1.5 flex items-center justify-between gap-3">
<span className="text-xs font-semibold text-white/80"> <span className="text-xs font-semibold text-[#4F758B]">
{isPlaying ? 'Reproduciendo' : 'Nota de voz'} {isPlaying ? 'Reproduciendo' : 'Nota de voz'}
</span> </span>
<span className="shrink-0 text-xs font-mono opacity-80"> <span className="shrink-0 text-xs font-mono text-[#6B8FA3]">
{formatDuration(audioCurrentTime)} / {formatDuration(effectiveAudioDuration)} {formatDuration(audioCurrentTime)} / {formatDuration(effectiveAudioDuration)}
</span> </span>
</div> </div>
<div className="relative h-5"> <div className="relative h-5">
<div className="absolute left-0 right-0 top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-white/15" /> <div className="absolute left-0 right-0 top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-[#6CC24A]/20" />
<div <div
className="absolute left-0 top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-white/70" className="absolute left-0 top-1/2 h-1.5 -translate-y-1/2 rounded-full bg-[#6CC24A]/70"
style={{ width: `${audioProgress}%` }} style={{ width: `${audioProgress}%` }}
/> />
<input <input
+15 -15
View File
@@ -32,25 +32,25 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, w
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
onClick={onClose} onClick={onClose}
className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm" className="fixed inset-0 z-50 bg-[#2C3E45]/35 backdrop-blur-sm"
/> />
<motion.div <motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }} initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }} animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }} exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 w-full max-w-md bg-zinc-900 border border-white/10 rounded-2xl shadow-2xl overflow-hidden" className="fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 overflow-hidden rounded-3xl border border-[#D0D0D0]/70 bg-white shadow-2xl"
> >
<div className="flex items-center justify-between p-6 border-b border-white/5"> <div className="flex items-center justify-between border-b border-[#D0D0D0]/70 p-6">
<h2 className="text-xl font-semibold text-white">Configuración de conexión</h2> <h2 className="text-xl font-semibold text-[#4F758B]">Configuración de conexión</h2>
<button onClick={onClose} className="p-2 rounded-full hover:bg-white/5 text-zinc-400 hover:text-white transition-colors"> <button onClick={onClose} className="rounded-full p-2 text-[#6B8FA3] transition-colors hover:bg-[#EEF6E8] hover:text-[#4F758B]">
<X className="w-5 h-5" /> <X className="h-5 w-5" />
</button> </button>
</div> </div>
<div className="p-6 space-y-6"> <div className="space-y-6 p-6">
<div className="space-y-2"> <div className="space-y-2">
<label className="text-sm font-medium text-zinc-300 flex items-center gap-2"> <label className="flex items-center gap-2 text-sm font-medium text-[#4F758B]">
<LinkIcon className="w-4 h-4 text-indigo-400" /> <LinkIcon className="h-4 w-4 text-[#6CC24A]" />
Webhook URL Webhook URL
</label> </label>
<input <input
@@ -58,26 +58,26 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, w
value={url} value={url}
onChange={(e) => setUrl(e.target.value)} onChange={(e) => setUrl(e.target.value)}
placeholder="https://tu-endpoint.com/webhook" placeholder="https://tu-endpoint.com/webhook"
className="w-full bg-black/50 border border-white/10 rounded-xl px-4 py-3 text-white placeholder:text-zinc-600 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all font-mono text-sm" className="w-full rounded-xl border border-[#D0D0D0]/80 bg-[#F9FBFC] px-4 py-3 font-mono text-sm text-[#4A4A4A] placeholder:text-[#9AA8B0] transition-all focus:border-[#6CC24A] focus:outline-none focus:ring-2 focus:ring-[#6CC24A]/25"
/> />
<p className="text-xs text-zinc-500 mt-2"> <p className="mt-2 text-xs text-[#6B8FA3]">
Endpoint donde se enviarán la nota de voz, imágenes, documentos y enlaces mediante una solicitud POST. Endpoint donde se enviarán la nota de voz, imágenes, documentos y enlaces mediante una solicitud POST.
</p> </p>
</div> </div>
</div> </div>
<div className="p-6 border-t border-white/5 bg-black/20 flex justify-end gap-3"> <div className="flex justify-end gap-3 border-t border-[#D0D0D0]/70 bg-[#F9FBFC] p-6">
<button <button
onClick={onClose} onClick={onClose}
className="px-5 py-2.5 rounded-xl text-sm font-medium text-zinc-300 hover:text-white hover:bg-white/5 transition-colors" className="rounded-xl px-5 py-2.5 text-sm font-medium text-[#6B8FA3] transition-colors hover:bg-white hover:text-[#4F758B]"
> >
Cancelar Cancelar
</button> </button>
<button <button
onClick={handleSave} onClick={handleSave}
className="px-5 py-2.5 rounded-xl text-sm font-medium bg-indigo-600 text-white hover:bg-indigo-500 transition-colors flex items-center gap-2 shadow-lg shadow-indigo-500/20" className="flex items-center gap-2 rounded-xl bg-[#4F758B] px-5 py-2.5 text-sm font-medium text-white shadow-lg shadow-[#4F758B]/20 transition-colors hover:bg-[#456A7F]"
> >
<Save className="w-4 h-4" /> <Save className="h-4 w-4" />
Guardar Guardar
</button> </button>
</div> </div>
+275 -54
View File
@@ -2,12 +2,22 @@
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
:root { :root {
color-scheme: dark; color-scheme: light;
--glm-blue: #4F758B;
--glm-blue-mid: #5B7F95;
--glm-steel: #6B8FA3;
--glm-green: #6CC24A;
--glm-green-soft: #EEF6E8;
--glm-blue-soft: #D6E8F4;
--glm-text: #4A4A4A;
--glm-border: #D0D0D0;
--glm-page: #F7FAF8;
} }
html { html {
min-height: 100%; min-height: 100%;
overflow-y: scroll; overflow-y: scroll;
background: var(--glm-page);
} }
body, body,
@@ -17,11 +27,156 @@ body,
body { body {
margin: 0; margin: 0;
background: #060816; background: var(--glm-page);
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-family: Arial, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
overflow-x: hidden; overflow-x: hidden;
} }
button,
input,
textarea,
select {
font-family: Arial, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
/* GLM light skin: keeps all behavior intact and only remaps the previous dark UI colors. */
.glm-app .border-white\/10,
.glm-app .border-white\/5,
.glm-app .border-indigo-400\/20,
.glm-app .border-indigo-400\/25,
.glm-app .border-indigo-400\/30,
.glm-app .border-indigo-300\/25 {
border-color: rgba(208, 208, 208, 0.72) !important;
}
.glm-app .bg-black\/10,
.glm-app .bg-black\/20,
.glm-app .bg-black\/25,
.glm-app .bg-white\/5,
.glm-app .bg-white\/10,
.glm-app .bg-white\/\[0\.03\],
.glm-app .bg-white\/\[0\.04\] {
background-color: #ffffff !important;
}
.glm-app .bg-black\/30,
.glm-app .bg-black\/35,
.glm-app .bg-black\/40,
.glm-app .bg-black\/50 {
background-color: #F9FBFC !important;
}
.glm-app .text-zinc-100,
.glm-app .text-zinc-200,
.glm-app .text-zinc-300 {
color: var(--glm-text) !important;
}
.glm-app .text-zinc-400,
.glm-app .text-zinc-500,
.glm-app .text-zinc-600 {
color: var(--glm-steel) !important;
}
.glm-app .text-white,
.glm-app .text-white\/80,
.glm-app .text-white\/90,
.glm-app .text-indigo-100,
.glm-app .text-indigo-100\/80,
.glm-app .text-indigo-200 {
color: var(--glm-blue) !important;
}
.glm-app .text-indigo-300,
.glm-app .text-indigo-400,
.glm-app .text-emerald-300,
.glm-app .text-emerald-400 {
color: var(--glm-green) !important;
}
.glm-app .text-amber-300 {
color: #FF8C00 !important;
}
.glm-app [class*="bg-[linear-gradient(135deg,#6366f1"],
.glm-app [class*="bg-[linear-gradient(135deg,rgba(79,70,229"],
.glm-app [class*="bg-[linear-gradient(135deg,#6366f1_0%,#2563eb_50%,#06b6d4"],
.glm-app [class*="bg-[linear-gradient(135deg,#6366f1_0%,#2563eb_100%"] {
background: var(--glm-blue) !important;
color: #ffffff !important;
box-shadow: 0 16px 34px rgba(79, 117, 139, 0.2) !important;
}
.glm-app [class*="bg-[linear-gradient(135deg,#6366f1"] *,
.glm-app [class*="bg-[linear-gradient(135deg,rgba(79,70,229"] *,
.glm-app [class*="bg-[linear-gradient(135deg,#6366f1_0%,#2563eb_50%,#06b6d4"] *,
.glm-app [class*="bg-[linear-gradient(135deg,#6366f1_0%,#2563eb_100%"] * {
color: inherit !important;
}
.glm-app [class*="bg-[linear-gradient(90deg,#6366f1"] {
background: linear-gradient(90deg, var(--glm-green) 0%, var(--glm-blue) 100%) !important;
}
.glm-app [class*="bg-[linear-gradient(180deg,rgba(99,102,241"],
.glm-app [class*="bg-[linear-gradient(145deg,rgba(99,102,241"] {
background: #F8FBF6 !important;
border-color: rgba(108, 194, 74, 0.38) !important;
box-shadow: 0 14px 34px rgba(108, 194, 74, 0.12) !important;
}
.glm-app [class*="bg-indigo-500/10"],
.glm-app .bg-indigo-500\/10,
.glm-app .bg-emerald-500\/10,
.glm-app .bg-emerald-500\/15 {
background-color: var(--glm-green-soft) !important;
}
.glm-app .bg-indigo-300,
.glm-app .bg-indigo-400,
.glm-app .bg-emerald-400 {
background-color: var(--glm-green) !important;
}
.glm-app .bg-zinc-600 {
background-color: var(--glm-border) !important;
}
.glm-app [class*="min-h-[178px]"] {
min-height: 132px !important;
}
.glm-app [class*="shadow-[0_28px_80px"],
.glm-app [class*="shadow-[0_24px_60px"],
.glm-app [class*="shadow-[0_30px_80px"],
.glm-app [class*="shadow-[0_16px_40px"] {
box-shadow: 0 18px 50px rgba(79, 117, 139, 0.11) !important;
}
.glm-app .prose-invert {
--tw-prose-body: var(--glm-text);
--tw-prose-headings: var(--glm-blue);
--tw-prose-lead: var(--glm-text);
--tw-prose-links: var(--glm-blue);
--tw-prose-bold: var(--glm-blue);
--tw-prose-counters: var(--glm-steel);
--tw-prose-bullets: var(--glm-green);
--tw-prose-hr: var(--glm-border);
--tw-prose-quotes: var(--glm-blue);
--tw-prose-quote-borders: var(--glm-green);
--tw-prose-captions: var(--glm-steel);
--tw-prose-code: var(--glm-blue);
--tw-prose-pre-code: var(--glm-text);
--tw-prose-pre-bg: #F9FBFC;
--tw-prose-th-borders: var(--glm-border);
--tw-prose-td-borders: var(--glm-border);
}
.glm-app .prose pre {
border: 1px solid rgba(208, 208, 208, 0.7) !important;
background: #F9FBFC !important;
}
@layer utilities { @layer utilities {
.scrollbar-hide::-webkit-scrollbar { .scrollbar-hide::-webkit-scrollbar {
display: none; display: none;
@@ -42,59 +197,17 @@ body {
} }
.scrollbar-thin::-webkit-scrollbar-thumb { .scrollbar-thin::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.12); background: rgba(79, 117, 139, 0.22);
border-radius: 999px; border-radius: 999px;
} }
.scrollbar-thin::-webkit-scrollbar-thumb:hover { .scrollbar-thin::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.22); background: rgba(79, 117, 139, 0.36);
}
}
@layer components {
.audio-preview-range {
height: 20px;
appearance: none;
-webkit-appearance: none;
background: transparent;
}
.audio-preview-range::-webkit-slider-runnable-track {
height: 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
}
.audio-preview-range::-webkit-slider-thumb {
width: 16px;
height: 16px;
margin-top: -5px;
border: 3px solid #ffffff;
border-radius: 999px;
appearance: none;
-webkit-appearance: none;
background: #67e8f9;
box-shadow: 0 0 0 5px rgba(103, 232, 249, 0.16), 0 8px 18px rgba(0, 0, 0, 0.32);
}
.audio-preview-range::-moz-range-track {
height: 6px;
border: 0;
border-radius: 999px;
background: rgba(255, 255, 255, 0.16);
}
.audio-preview-range::-moz-range-thumb {
width: 16px;
height: 16px;
border: 3px solid #ffffff;
border-radius: 999px;
background: #67e8f9;
box-shadow: 0 0 0 5px rgba(103, 232, 249, 0.16), 0 8px 18px rgba(0, 0, 0, 0.32);
} }
} }
@layer components { @layer components {
.audio-preview-range,
.message-audio-range { .message-audio-range {
height: 20px; height: 20px;
appearance: none; appearance: none;
@@ -102,12 +215,14 @@ body {
background: transparent; background: transparent;
} }
.audio-preview-range::-webkit-slider-runnable-track,
.message-audio-range::-webkit-slider-runnable-track { .message-audio-range::-webkit-slider-runnable-track {
height: 6px; height: 6px;
border-radius: 999px; border-radius: 999px;
background: transparent; background: rgba(108, 194, 74, 0.18);
} }
.audio-preview-range::-webkit-slider-thumb,
.message-audio-range::-webkit-slider-thumb { .message-audio-range::-webkit-slider-thumb {
width: 16px; width: 16px;
height: 16px; height: 16px;
@@ -116,23 +231,129 @@ body {
border-radius: 999px; border-radius: 999px;
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
background: #67e8f9; background: var(--glm-green);
box-shadow: 0 0 0 5px rgba(103, 232, 249, 0.16), 0 8px 18px rgba(0, 0, 0, 0.32); box-shadow: 0 0 0 5px rgba(108, 194, 74, 0.16), 0 8px 18px rgba(79, 117, 139, 0.18);
} }
.audio-preview-range::-moz-range-track,
.message-audio-range::-moz-range-track { .message-audio-range::-moz-range-track {
height: 6px; height: 6px;
border: 0; border: 0;
border-radius: 999px; border-radius: 999px;
background: transparent; background: rgba(108, 194, 74, 0.18);
} }
.audio-preview-range::-moz-range-thumb,
.message-audio-range::-moz-range-thumb { .message-audio-range::-moz-range-thumb {
width: 16px; width: 16px;
height: 16px; height: 16px;
border: 3px solid #ffffff; border: 3px solid #ffffff;
border-radius: 999px; border-radius: 999px;
background: #67e8f9; background: var(--glm-green);
box-shadow: 0 0 0 5px rgba(103, 232, 249, 0.16), 0 8px 18px rgba(0, 0, 0, 0.32); box-shadow: 0 0 0 5px rgba(108, 194, 74, 0.16), 0 8px 18px rgba(79, 117, 139, 0.18);
} }
} }
.glm-app [class*="bg-[linear-gradient(135deg,rgba(99,102,241"] {
background: var(--glm-green-soft) !important;
border-color: rgba(108, 194, 74, 0.36) !important;
box-shadow: 0 16px 38px rgba(108, 194, 74, 0.12) !important;
}
/* Redesign Premium Utilities */
.premium-grid-bg {
background-image:
linear-gradient(to right, rgba(108, 194, 74, 0.06) 1px, transparent 1px),
linear-gradient(to bottom, rgba(108, 194, 74, 0.06) 1px, transparent 1px);
background-size: 40px 40px;
mask-image: radial-gradient(ellipse at center, black 40%, transparent 80%);
-webkit-mask-image: radial-gradient(ellipse at center, black 40%, transparent 80%);
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.shimmer-btn {
position: relative;
overflow: hidden;
}
.shimmer-btn::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.22) 30%,
rgba(255, 255, 255, 0.35) 50%,
rgba(255, 255, 255, 0.22) 70%,
rgba(255, 255, 255, 0) 100%
);
animation: shimmer 3s infinite ease-in-out;
content: '';
}
/* Keep the audio preview play/pause glyph visible after the GLM light-skin color remap. */
.glm-app .glm-audio-preview-toggle svg {
color: #ffffff !important;
stroke: #ffffff !important;
}
.glm-app .glm-audio-preview-toggle svg.fill-current {
fill: #ffffff !important;
}
/* Keep the final review submit button readable against the GLM blue background. */
.glm-app .glm-submit-final,
.glm-app .glm-submit-final * {
color: #ffffff !important;
stroke: #ffffff !important;
}
.glm-app .glm-submit-final svg.fill-current {
fill: #ffffff !important;
}
/* Response portal attachment polish: keep uploaded files compact and readable inside the GLM message bubble. */
.glm-app .glm-message-attachment,
.glm-app .glm-message-attachment * {
color: #4A4A4A !important;
}
.glm-app .glm-message-attachment p:first-of-type {
color: #2E4052 !important;
}
.glm-app .glm-message-attachment p:not(:first-of-type) {
color: #6B8FA3 !important;
}
.glm-app .glm-message-attachment svg {
color: #4F758B !important;
stroke: #4F758B !important;
}
.glm-app .glm-message-audio span:first-of-type {
color: #4F758B !important;
}
.glm-app .glm-message-audio span:last-of-type {
color: #6B8FA3 !important;
}
.glm-app .glm-message-audio-toggle,
.glm-app .glm-message-audio-toggle * {
color: #ffffff !important;
fill: #ffffff !important;
stroke: #ffffff !important;
}