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">
<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>
<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>

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>
<html lang="en">
<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 name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CDC Brief GLM</title>
<script type="module" crossorigin src="/brief/assets/index-BjRIMN2B.js"></script>
<link rel="stylesheet" crossorigin href="/brief/assets/index-D-Qs_b2T.css">
<title>CDC Brief | GLM</title>
<script type="module" crossorigin src="/brief/assets/index-DT0rQidg.js"></script>
<link rel="stylesheet" crossorigin href="/brief/assets/index-B-WJR2cH.css">
</head>
<body>
<div id="root"></div>
+7 -2
View File
@@ -1,10 +1,15 @@
<!doctype html>
<html lang="en">
<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 name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CDC Brief GLM</title>
<title>CDC Brief | GLM</title>
</head>
<body>
<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">
<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>
<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>

Before

Width:  |  Height:  |  Size: 454 B

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+20 -104
View File
@@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from '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 { 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) {
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...
</div>
);
@@ -226,112 +226,30 @@ Verifica que la URL esté correcta en el entorno y que el endpoint esté activo
}
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="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 className="absolute left-[-10%] top-[-10%] h-[32rem] w-[32rem] rounded-full bg-[#EEF6E8]/60 blur-[90px]" />
<div className="absolute right-[-8%] top-[15%] h-[28rem] w-[28rem] rounded-full bg-[#D6E8F4]/60 blur-[90px]" />
</div>
<Header isConnected={isConnected} onLogout={handleLogout} />
<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">
<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>
<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">
{/* 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
onSendMessage={handleSendMessage}
isProcessing={isProcessing}
/>
</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">
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>
)}
{/* Response Section — only show when there's content */}
{(messages.length > 0 || isProcessing) && (
<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="mb-4 text-xs font-bold uppercase tracking-[0.22em] text-[#6B8FA3]">Respuesta del portal</p>
<AnimatePresence initial={false}>
{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 }}
animate={{ opacity: 1, y: 0 }}
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" />
<span className="text-sm font-medium text-zinc-300">
Procesando la solicitud...
</span>
<Loader2 className="h-4 w-4 animate-spin text-[#6CC24A]" />
<span className="text-sm font-medium text-[#4A4A4A]">Procesando la solicitud...</span>
</motion.div>
)}
<div ref={messagesEndRef} className="h-2" />
</div>
</section>
<div ref={messagesEndRef} className="h-1" />
</section>
)}
</div>
</main>
</div>
+32 -39
View File
@@ -1,5 +1,5 @@
import React from 'react';
import { LogOut, Sparkles, Zap } from 'lucide-react';
import { LogOut } from 'lucide-react';
import { cn } from '../lib/utils';
interface HeaderProps {
@@ -7,58 +7,51 @@ interface HeaderProps {
onLogout: () => void;
}
const logoSrc = `${import.meta.env.BASE_URL}glm-logo.png`;
export const Header: React.FC<HeaderProps> = ({ isConnected, onLogout }) => {
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">
<div className="mx-auto flex w-full max-w-7xl items-center justify-between gap-4">
<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" />
<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 px-5 py-3 sm:px-8">
{/* Left: Logo + Title */}
<div className="flex items-center gap-4">
<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 className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<h1 className="truncate text-lg font-semibold tracking-tight text-white sm:text-xl">
Portal Brief CDC
</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">
Experiencia guiada
<div>
<h1 className="text-base font-bold tracking-tight text-[#4F758B] sm:text-lg">
Portal Brief CDC
</h1>
<div className="mt-0.5 flex items-center gap-2">
<span className={cn(
'relative flex h-2 w-2 shrink-0'
)}>
{isConnected && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-[#6CC24A] opacity-60" />
)}
<span className={cn(
'relative inline-flex h-2 w-2 rounded-full',
isConnected ? 'bg-[#6CC24A]' : 'bg-red-400'
)} />
</span>
<span className="text-[11px] font-medium text-slate-400">
{isConnected ? 'Conectado' : 'Sin conexión'} · GomezLee Marketing
</span>
</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 && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-70" />
)}
<span
className={cn(
'relative inline-flex h-2.5 w-2.5 rounded-full',
isConnected ? 'bg-emerald-400' : 'bg-red-400'
)}
/>
</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>
{/* Right: Logout */}
<button
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"
>
<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>
</button>
</div>
</header>
);
};
};
File diff suppressed because it is too large Load Diff
+85 -44
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { motion } from 'motion/react';
import { Chrome, ShieldCheck, Sparkles } from 'lucide-react';
import { Loader2 } from 'lucide-react';
interface LoginScreenProps {
onLogin: () => void;
@@ -8,61 +8,102 @@ interface LoginScreenProps {
error?: string;
}
const logoSrc = `${import.meta.env.BASE_URL}glm-logo.png`;
export const LoginScreen: React.FC<LoginScreenProps> = ({ onLogin, isLoading, error }) => {
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="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]" />
<motion.div
animate={{ x: [0, 45, -25, 0], y: [0, -60, 40, 0], scale: [1, 1.15, 0.95, 1] }}
transition={{ duration: 22, repeat: Infinity, ease: 'easeInOut' }}
className="absolute left-[-8%] top-[-12%] h-[36rem] w-[36rem] rounded-full bg-[#EEF6E8]/70 blur-[90px]"
/>
<motion.div
animate={{ x: [0, -35, 45, 0], y: [0, 50, -40, 0], scale: [1, 0.95, 1.1, 1] }}
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>
{/* Login Card */}
<motion.div
initial={{ opacity: 0, y: 18, scale: 0.98 }}
initial={{ opacity: 0, y: 32, scale: 0.97 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
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"
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]"
>
<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" />
Acceso corporativo
</div>
{/* Top gradient bar */}
<div className="absolute top-0 inset-x-0 h-[4px] bg-gradient-to-r from-[#6CC24A] via-[#5B7F95] to-[#4F758B]" />
<h1 className="text-3xl font-semibold tracking-tight text-white sm:text-4xl">
Portal Brief CDC
</h1>
{/* Centered content */}
<div className="flex flex-col items-center text-center">
<p className="mt-4 text-sm leading-7 text-zinc-300 sm:text-base">
Inicia sesión con tu cuenta corporativa de Google para acceder al portal.
</p>
<div className="mt-6 rounded-2xl border border-white/10 bg-black/20 p-4">
<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>
{/* 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
</span>
</h1>
{/* Subtle tagline */}
<p className="mt-2.5 text-[11px] font-semibold uppercase tracking-[0.22em] text-slate-400">
GomezLee Marketing
</p>
{/* Error */}
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
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
onClick={onLogin}
disabled={isLoading}
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"
>
{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'}
</span>
</button>
</div>
{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">
{error}
</div>
)}
<button
onClick={onLogin}
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"
>
<Chrome className="h-4 w-4" />
{isLoading ? 'Abriendo Google...' : 'Continuar con Google'}
</button>
</motion.div>
</div>
);
};
};
+40 -42
View File
@@ -1,7 +1,7 @@
import React from 'react';
import { motion } from 'motion/react';
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 { cn } from '../lib/utils';
import ReactMarkdown from 'react-markdown';
@@ -24,47 +24,48 @@ const formatDuration = (seconds: number) => {
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 fileSizeKb = `${(attachment.file.size / 1024).toFixed(1)} KB`;
const label = getAttachmentLabel(attachment);
if (attachment.type === 'image') {
return (
<div className="group relative max-w-[320px] overflow-hidden rounded-2xl border border-white/10 bg-black/20">
<img
src={attachment.url}
alt={attachment.file.name}
className="h-full w-full object-cover transition duration-300 group-hover:scale-[1.02]"
/>
<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 className="truncate">{attachment.file.name}</div>
<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
src={attachment.url}
alt={attachment.file.name}
className="h-full w-full object-cover"
/>
</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>
);
}
if (attachment.type === 'audio') {
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>
);
}
const Icon = attachment.type === 'audio' ? Music : FileText;
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="flex h-11 w-11 items-center justify-center rounded-xl bg-black/25">
<FileText className="h-4 w-4 text-indigo-200" />
<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-14 w-14 shrink-0 items-center justify-center rounded-xl border border-[#D0D0D0]/60 bg-[#F7FAF8]">
<Icon className="h-5 w-5 text-[#4F758B]" />
</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>
<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>
);
@@ -118,7 +119,7 @@ const DriveLinkCard = ({ url, onClose }: { url: string; onClose: () => void }) =
<button
type="button"
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 ? 'Copiado' : 'Copiar'}
@@ -269,38 +270,35 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({ message }) => {
)}
{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">
<button
type="button"
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'}
>
{isPlaying ? (
<div className="flex h-3.5 w-3.5 gap-1">
<div className="h-full w-1 rounded-sm bg-white" />
<div className="h-full w-1 rounded-sm bg-white" />
</div>
<Pause className="h-5 w-5" fill="currentColor" />
) : (
<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>
<div className="min-w-0 flex-1">
<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'}
</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)}
</span>
</div>
<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
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}%` }}
/>
<input
+15 -15
View File
@@ -32,25 +32,25 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, w
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
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
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
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">
<h2 className="text-xl font-semibold text-white">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">
<X className="w-5 h-5" />
<div className="flex items-center justify-between border-b border-[#D0D0D0]/70 p-6">
<h2 className="text-xl font-semibold text-[#4F758B]">Configuración de conexión</h2>
<button onClick={onClose} className="rounded-full p-2 text-[#6B8FA3] transition-colors hover:bg-[#EEF6E8] hover:text-[#4F758B]">
<X className="h-5 w-5" />
</button>
</div>
<div className="p-6 space-y-6">
<div className="space-y-6 p-6">
<div className="space-y-2">
<label className="text-sm font-medium text-zinc-300 flex items-center gap-2">
<LinkIcon className="w-4 h-4 text-indigo-400" />
<label className="flex items-center gap-2 text-sm font-medium text-[#4F758B]">
<LinkIcon className="h-4 w-4 text-[#6CC24A]" />
Webhook URL
</label>
<input
@@ -58,26 +58,26 @@ export const SettingsModal: React.FC<SettingsModalProps> = ({ isOpen, onClose, w
value={url}
onChange={(e) => setUrl(e.target.value)}
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.
</p>
</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
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
</button>
<button
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
</button>
</div>
+275 -54
View File
@@ -2,12 +2,22 @@
@plugin "@tailwindcss/typography";
: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 {
min-height: 100%;
overflow-y: scroll;
background: var(--glm-page);
}
body,
@@ -17,11 +27,156 @@ body,
body {
margin: 0;
background: #060816;
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--glm-page);
font-family: Arial, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
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 {
.scrollbar-hide::-webkit-scrollbar {
display: none;
@@ -42,59 +197,17 @@ body {
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.12);
background: rgba(79, 117, 139, 0.22);
border-radius: 999px;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.22);
}
}
@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);
background: rgba(79, 117, 139, 0.36);
}
}
@layer components {
.audio-preview-range,
.message-audio-range {
height: 20px;
appearance: none;
@@ -102,12 +215,14 @@ body {
background: transparent;
}
.audio-preview-range::-webkit-slider-runnable-track,
.message-audio-range::-webkit-slider-runnable-track {
height: 6px;
border-radius: 999px;
background: transparent;
background: rgba(108, 194, 74, 0.18);
}
.audio-preview-range::-webkit-slider-thumb,
.message-audio-range::-webkit-slider-thumb {
width: 16px;
height: 16px;
@@ -116,23 +231,129 @@ body {
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);
background: var(--glm-green);
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 {
height: 6px;
border: 0;
border-radius: 999px;
background: transparent;
background: rgba(108, 194, 74, 0.18);
}
.audio-preview-range::-moz-range-thumb,
.message-audio-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);
background: var(--glm-green);
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;
}