{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "login", "type": "registry:block", "title": "login", "description": "Bloque Concorde login — incluye sus 3 componentes.", "dependencies": [ "react" ], "registryDependencies": [], "files": [ { "path": "bloques/login/desktop/dimensions.ts", "type": "registry:file", "target": "bloques/login/desktop/dimensions.ts", "content": "/**\n * Dimensiones del bloque Login (desktop) — módulo plano sin \"use client\".\n * Los Server Components y los Viewers pueden importar estas constantes\n * sin cruzar la barrera \"use client\" → undefined → NaN.\n */\n\nexport const LOGIN_WIDTH = 798;\nexport const LOGIN_HEIGHT = 837;\n" }, { "path": "bloques/login/desktop/Login.tsx", "type": "registry:file", "target": "bloques/login/desktop/Login.tsx", "content": "\"use client\";\n\n/**\n * Login — Bloque de Concorde (Voyager DS)\n *\n * Página de login (Header logueado-guest + fondo blanco):\n * · Card 766×589, 2 columnas:\n * - Izquierda: banner placeholder 366×555 (panel de registro — pendiente de diseño)\n * - Derecha: formulario (correo, contraseña, ingresa, registrate)\n * · Centro de ayuda banner (placeholder)\n */\n\nimport { useState } from \"react\";\nimport type { CSSProperties, JSX } from \"react\";\nimport AppHeader from \"../../header/desktop/Header\";\nimport Button from \"../../../components/Button\";\nimport Input from \"../../../components/Input\";\nimport EyeIcon from \"../../../components/EyeIcon\";\nimport { LOGIN_WIDTH, LOGIN_HEIGHT } from \"./dimensions\";\n\nexport { LOGIN_WIDTH, LOGIN_HEIGHT } from \"./dimensions\";\n\nexport interface LoginProps {\n /** Renderiza el AppHeader interno. `false` cuando el visor lo dibuja aparte. Default `true`. */\n renderHeader?: boolean;\n className?: string;\n}\n\nconst HELP_BANNER: CSSProperties = {\n width: 766,\n height: 120,\n boxSizing: \"border-box\",\n borderRadius: 8,\n background: \"#E9EAEC\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: 13,\n fontWeight: 600,\n letterSpacing: \"0.06em\",\n color: \"#9AA1AC\",\n};\n\nconst STYLE_ID = \"concorde-login-styles\";\n\n// Estilos EXCLUSIVOS del layout desktop (card de 2 columnas + banner placeholder\n// de la izquierda). LoginForm trae los suyos aparte — así el form es reusable\n// tal cual en mobile (LoginMobile), sin arrastrar CSS de un layout que no tiene.\nconst LOGIN_STYLES = `\n.login-card {\n width: 766px;\n height: 589px;\n box-sizing: border-box;\n background: #ffffff;\n border-radius: 8px;\n box-shadow: rgba(0,0,0,0.1) 0px 0px 16px 4px;\n display: flex;\n gap: 16px;\n padding: 17px 16px;\n}\n.login-card__banner {\n width: 366px;\n height: 555px;\n flex-shrink: 0;\n border-radius: 8px;\n background: #E9EAEC;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.06em;\n color: #9AA1AC;\n}\n`;\n\nlet _stylesInjected = false;\n\nconst FORM_STYLE_ID = \"concorde-login-form-styles\";\n\nconst LOGIN_FORM_STYLES = `\n.login-card__form {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 20px;\n padding: 0 8px;\n}\n.login-field { display: flex; flex-direction: column; gap: 6px; width: 288px; max-width: 100%; }\n.login-field .pinput-root { width: 100%; }\n.login-field__label { font-size: 13px; font-weight: 500; color: #6B7280; }\n.login-field--password .pinput { padding-right: 44px; }\n.login-password-wrap { position: relative; }\n.login-eye {\n position: absolute;\n top: 50%;\n right: 16px;\n transform: translateY(-50%);\n display: flex;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n color: #9AA1AC;\n transition: color 0.15s;\n}\n.login-eye:hover { color: #6B7280; }\n.login-forgot {\n align-self: center;\n background: none;\n border: none;\n padding: 0;\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: #8460E5;\n cursor: pointer;\n}\n.login-forgot:hover { text-decoration: underline; }\n.login-divider {\n display: flex;\n align-items: center;\n gap: 12px;\n font-size: 13px;\n color: #9AA1AC;\n}\n.login-divider::before, .login-divider::after {\n content: \"\";\n width: 75px;\n flex: none;\n height: 1px;\n background: #E1E3E2;\n}\n`;\n\nlet _formStylesInjected = false;\n\n// \"¡Bienvenido! Cazador de Ofertas\" — SVG exacto exportado de Figma (288×69).\n// Texto vectorizado (paths de glifos), no reproducible con CSS: \"¡Bienvenido!\" y\n// \"de Ofertas\" usan el gradiente vault, \"Cazador\" el gradiente naranja.\nfunction BienvenidoHeading(): JSX.Element {\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n\n/**\n * LoginForm — formulario de acceso (heading + correo + contraseña + CTAs).\n * Self-contained (inyecta su propio CSS): reusado tal cual por Login (desktop,\n * dentro de la columna derecha del card) y LoginMobile (dentro del card mobile).\n */\nexport function LoginForm(): JSX.Element {\n const [showPassword, setShowPassword] = useState(false);\n\n if (typeof document !== \"undefined\" && !_formStylesInjected) {\n if (!document.getElementById(FORM_STYLE_ID)) {\n const el = document.createElement(\"style\");\n el.id = FORM_STYLE_ID;\n el.textContent = LOGIN_FORM_STYLES;\n document.head.appendChild(el);\n }\n _formStylesInjected = true;\n }\n\n function toggleShowPassword(): void {\n setShowPassword(function toggle(v) { return !v; });\n }\n\n return (\n
\n