Spaces:
Running
Running
Download apps/web/src/components/auth/AuthLayout.tsx from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 13.9 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/web/src/components/auth/AuthLayout.tsx
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/web/src/components/auth/AuthLayout.tsx
-
curl -L -o AuthLayout.tsx https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/web/src/components/auth/AuthLayout.tsx
13.9 kB
| "use client"; | |
| import React, { useEffect, useState } from "react"; | |
| import Link from "next/link"; | |
| import Image from "next/image"; | |
| import { ArrowLeft } from "lucide-react"; | |
| import { ThemeToggle } from "@/lib/theme-toggle"; | |
| interface AuthLayoutProps { | |
| title: string; | |
| subtitle: string; | |
| children: React.ReactNode; | |
| footerText?: string; | |
| footerLinkText?: string; | |
| footerLinkHref?: string; | |
| } | |
| /* Waveform design β "Golden Studio": the auth visual column is a dim | |
| control room: amber record light (pulsing LED + mono timecode), a live | |
| waveform as the single hero object, and the tail of the guest pipeline | |
| fading into canvas at the bottom. Collapses below 900px per the design | |
| system (Β§5.4) β the form is never broken. | |
| Bar heights are a fixed, hand-tuned array (never Math.random β would | |
| cause hydration mismatches between SSR and client). */ | |
| const WAVE_HEIGHTS = [ | |
| 12, 18, 26, 20, 34, 48, 42, 60, 84, 72, 96, 120, 104, 132, 116, 140, 124, 108, | |
| 128, 96, 112, 78, 88, 60, 68, 44, 52, 32, 40, 24, 28, 16, 20, 12, | |
| ]; | |
| const PIPELINE_CARDS = [ | |
| { label: "Prospect", count: 8, color: "var(--status-prospect)", rotate: "-2deg" }, | |
| { label: "Contacted", count: 5, color: "var(--status-contacted)", rotate: "1.5deg" }, | |
| { label: "Scheduled", count: 3, color: "var(--status-scheduled)", rotate: "-1deg" }, | |
| ]; | |
| function formatTimecode(totalSeconds: number): string { | |
| const h = Math.floor(totalSeconds / 3600); | |
| const m = Math.floor((totalSeconds % 3600) / 60); | |
| const s = totalSeconds % 60; | |
| return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`; | |
| } | |
| export function AuthLayout({ | |
| title, | |
| subtitle, | |
| children, | |
| footerText, | |
| footerLinkText, | |
| footerLinkHref, | |
| }: AuthLayoutProps) { | |
| return ( | |
| <div className="auth-layout-container"> | |
| <style>{authStyles}</style> | |
| <AuthFormColumn title={title} subtitle={subtitle}> | |
| {children} | |
| {footerText && footerLinkText && footerLinkHref && ( | |
| <p className="auth-form-footer"> | |
| {footerText}{" "} | |
| <Link href={footerLinkHref} style={{ color: "var(--color-primary)", fontWeight: 600, textDecoration: "none" }}> | |
| {footerLinkText} | |
| </Link> | |
| </p> | |
| )} | |
| </AuthFormColumn> | |
| <AuthVisualColumn /> | |
| </div> | |
| ); | |
| } | |
| /* βββ Form column (left, 40%) ββββββββββββββββββββββββββββββββ */ | |
| function AuthFormColumn({ | |
| title, | |
| subtitle, | |
| children, | |
| }: { | |
| title: string; | |
| subtitle: string; | |
| children: React.ReactNode; | |
| }) { | |
| return ( | |
| <div className="auth-form-col"> | |
| <div className="auth-form-brand"> | |
| <Link href="/" className="auth-brand-link"> | |
| <Image src="/logo.png" alt="MicProtocol" width={40} height={40} style={{ borderRadius: "var(--radius-md)", objectFit: "contain" }} /> | |
| <span className="auth-brand-name">MicProtocol</span> | |
| </Link> | |
| <ThemeToggle className="auth-theme-toggle" /> | |
| </div> | |
| <div className="auth-form-center"> | |
| <div style={{ width: "100%", maxWidth: 420 }}> | |
| <Link | |
| href="/" | |
| className="auth-back-link" | |
| > | |
| <ArrowLeft size={14} /> | |
| <span>Back to home</span> | |
| </Link> | |
| </div> | |
| <div className="auth-card texture-surface texture-surface--fluted-glass"> | |
| <h1 className="auth-title">{title}</h1> | |
| <p className="auth-subtitle">{subtitle}</p> | |
| {children} | |
| </div> | |
| </div> | |
| <div className="auth-form-meta"> | |
| <span>© {new Date().getFullYear()} MicProtocol</span> | |
| <div className="auth-form-meta-links"> | |
| <Link href="/privacy">Privacy</Link> | |
| <Link href="/terms">Terms</Link> | |
| <Link href="/security">Security</Link> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| /* βββ Visual column (right, 60%) βββββββββββββββββββββββββββββ */ | |
| function AuthVisualColumn() { | |
| const [seconds, setSeconds] = useState(0); | |
| useEffect(() => { | |
| const id = window.setInterval(() => { | |
| setSeconds((prev) => prev + 1); | |
| }, 1000); | |
| return () => window.clearInterval(id); | |
| }, []); | |
| return ( | |
| <div className="auth-visual-col texture-surface texture-surface--speckled-graphite" aria-hidden="true"> | |
| <div className="auth-visual-bg" /> | |
| {/* Record light */} | |
| <div className="auth-rec-indicator"> | |
| <span className="auth-rec-dot" /> | |
| <span className="auth-rec-time">REC {formatTimecode(seconds)}</span> | |
| </div> | |
| {/* Live waveform β the hero object */} | |
| <div className="auth-waveform"> | |
| {WAVE_HEIGHTS.map((height, i) => ( | |
| <span | |
| key={i} | |
| className="auth-waveform-bar" | |
| style={{ height, animationDelay: `${(i % 12) * -160}ms` }} | |
| /> | |
| ))} | |
| </div> | |
| {/* Pipeline tail */} | |
| <div className="auth-pipeline-stack"> | |
| {PIPELINE_CARDS.map(({ label, count, color, rotate }) => ( | |
| <div key={label} className="auth-pipeline-card" style={{ transform: `rotate(${rotate})` }}> | |
| <span className="auth-pipeline-dot" style={{ backgroundColor: color }} /> | |
| <span className="auth-pipeline-label">{label}</span> | |
| <span className="auth-pipeline-count">{count}</span> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| /* βββ Styles βββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| const authStyles = ` | |
| .auth-layout-container { | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| min-height: 100vh; | |
| background: var(--bg-primary); | |
| } | |
| /* ββ Form column ββ */ | |
| .auth-form-col { | |
| display: flex; | |
| flex-direction: column; | |
| min-height: 100vh; | |
| padding: clamp(1.5rem, 4vw, 2.5rem); | |
| } | |
| .auth-form-brand { | |
| width: 100%; | |
| max-width: 420px; | |
| margin: 0 auto; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .auth-theme-toggle { | |
| padding: var(--space-2); | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-md); | |
| color: var(--text-primary); | |
| cursor: pointer; | |
| transition: all var(--transition-fast); | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| } | |
| .auth-theme-toggle:hover { | |
| background: var(--bg-elevated); | |
| border-color: var(--color-primary); | |
| } | |
| .auth-brand-link { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--space-3); | |
| color: inherit; | |
| text-decoration: none; | |
| } | |
| .auth-brand-name { | |
| font-family: var(--font-display); | |
| font-size: var(--text-lg); | |
| font-weight: 700; | |
| letter-spacing: -0.02em; | |
| } | |
| .auth-form-center { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| width: 100%; | |
| } | |
| .auth-form-meta { | |
| width: 100%; | |
| max-width: 420px; | |
| margin: 0 auto; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| font-size: var(--text-xs); | |
| color: var(--text-tertiary); | |
| } | |
| .auth-form-meta-links { | |
| display: flex; | |
| gap: var(--space-4); | |
| } | |
| .auth-form-meta-links a { | |
| color: var(--text-tertiary); | |
| text-decoration: none; | |
| transition: color var(--transition-fast); | |
| } | |
| .auth-form-meta-links a:hover { | |
| color: var(--text-secondary); | |
| } | |
| .auth-card { | |
| width: 100%; | |
| max-width: 420px; | |
| padding: clamp(1.5rem, 4vw, 2.5rem); | |
| background: var(--bg-elevated); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-lg); | |
| box-shadow: var(--shadow-md); | |
| animation: auth-card-in 0.45s var(--ease-out-expo); | |
| } | |
| @keyframes auth-card-in { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .auth-title { | |
| font-family: var(--font-display); | |
| font-size: var(--text-2xl); | |
| font-weight: 700; | |
| letter-spacing: -0.03em; | |
| line-height: 1.1; | |
| color: var(--text-primary); | |
| margin: 0 0 var(--space-2); | |
| } | |
| .auth-subtitle { | |
| font-size: var(--text-sm); | |
| line-height: 1.55; | |
| color: var(--text-secondary); | |
| margin: 0 0 var(--space-6); | |
| } | |
| .auth-form-footer { | |
| margin: var(--space-6) 0 0; | |
| text-align: center; | |
| font-size: var(--text-xs); | |
| color: var(--text-secondary); | |
| } | |
| /* ββ Visual column ββ */ | |
| .auth-visual-col { | |
| display: none; | |
| position: relative; | |
| overflow: hidden; | |
| background: var(--bg-secondary); | |
| } | |
| /* Dim control-room backdrop: low amber light source bottom-center | |
| (the "record light" spilling onto the desk), cool top read */ | |
| .auth-visual-bg { | |
| position: absolute; | |
| inset: 0; | |
| background: | |
| radial-gradient(ellipse 60% 45% at 50% 92%, color-mix(in srgb, var(--color-primary) 10%, transparent), transparent 70%), | |
| radial-gradient(ellipse 80% 60% at 50% 45%, color-mix(in srgb, var(--color-primary) 4%, transparent), transparent 70%), | |
| var(--bg-secondary); | |
| } | |
| .auth-visual-bg::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| opacity: 0.4; | |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.6' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E"); | |
| pointer-events: none; | |
| } | |
| .auth-rec-indicator { | |
| position: absolute; | |
| top: clamp(1.5rem, 3vw, 2.5rem); | |
| right: clamp(1.5rem, 3vw, 2.5rem); | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 10px; | |
| padding: 8px 14px; | |
| border-radius: var(--radius-full); | |
| border: 1px solid var(--border-color); | |
| background: color-mix(in srgb, var(--bg-primary) 70%, transparent); | |
| z-index: 2; | |
| } | |
| .auth-rec-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: var(--radius-full); | |
| background: var(--color-primary); | |
| animation: auth-rec-pulse 1.5s ease-in-out infinite; | |
| } | |
| @keyframes auth-rec-pulse { | |
| 0%, 100% { | |
| box-shadow: 0 0 4px color-mix(in srgb, var(--color-primary) 60%, transparent); | |
| opacity: 1; | |
| } | |
| 50% { | |
| box-shadow: 0 0 12px color-mix(in srgb, var(--color-primary) 90%, transparent); | |
| opacity: 0.75; | |
| } | |
| } | |
| .auth-rec-time { | |
| font-family: var(--font-mono); | |
| font-variant-numeric: tabular-nums; | |
| font-size: var(--text-xs); | |
| font-weight: 500; | |
| color: var(--text-secondary); | |
| letter-spacing: 0.02em; | |
| } | |
| /* Waveform β centered hero object */ | |
| .auth-waveform { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -55%); | |
| display: flex; | |
| align-items: center; | |
| gap: 4px; | |
| width: min(560px, 72%); | |
| justify-content: center; | |
| z-index: 1; | |
| } | |
| .auth-waveform-bar { | |
| flex: 1; | |
| max-width: 6px; | |
| min-width: 2px; | |
| border-radius: var(--radius-full); | |
| background: linear-gradient( | |
| 180deg, | |
| color-mix(in srgb, var(--color-primary) 55%, var(--bg-secondary)) 0%, | |
| var(--color-primary) 100% | |
| ); | |
| opacity: 0.85; | |
| animation: auth-wave-motion 1.6s ease-in-out infinite alternate; | |
| } | |
| @keyframes auth-wave-motion { | |
| from { | |
| transform: scaleY(0.85); | |
| opacity: 0.55; | |
| } | |
| to { | |
| transform: scaleY(1.12); | |
| opacity: 0.95; | |
| } | |
| } | |
| /* Pipeline tail β three stage cards drifting down-right, fading out */ | |
| .auth-pipeline-stack { | |
| position: absolute; | |
| bottom: 0; | |
| left: clamp(1.5rem, 4vw, 3rem); | |
| width: min(280px, 55%); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-3); | |
| padding-bottom: var(--space-6); | |
| -webkit-mask-image: linear-gradient(180deg, #000 30%, transparent 98%); | |
| mask-image: linear-gradient(180deg, #000 30%, transparent 98%); | |
| z-index: 1; | |
| } | |
| .auth-pipeline-card { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-3); | |
| padding: var(--space-3) var(--space-4); | |
| border-radius: var(--radius-md); | |
| background: var(--bg-elevated); | |
| border: 1px solid var(--border-color); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .auth-pipeline-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: var(--radius-full); | |
| flex-shrink: 0; | |
| } | |
| .auth-pipeline-label { | |
| flex: 1; | |
| font-size: var(--text-xs); | |
| font-weight: 600; | |
| color: var(--text-secondary); | |
| } | |
| .auth-pipeline-count { | |
| font-family: var(--font-display); | |
| font-size: var(--text-sm); | |
| font-weight: 700; | |
| font-variant-numeric: tabular-nums; | |
| color: var(--text-primary); | |
| } | |
| /* ββ Split at 900px: form + visual columns ββ */ | |
| @media (min-width: 900px) { | |
| .auth-layout-container { | |
| grid-template-columns: 2fr 3fr; | |
| } | |
| .auth-visual-col { | |
| display: block; | |
| } | |
| } | |
| /* ββ Big screens (>= 1200px): balanced form + visual, no container shrink ββ */ | |
| @media (min-width: 1200px) { | |
| .auth-layout-container { | |
| grid-template-columns: 1fr 1fr; | |
| } | |
| .auth-form-col { | |
| padding: clamp(2rem, 5vw, 3rem); | |
| } | |
| .auth-card { | |
| max-width: 420px; | |
| margin: 0 auto; | |
| } | |
| .auth-visual-col { | |
| min-width: 0; | |
| } | |
| } | |
| /* ββ Reduced motion: calm the room ββ */ | |
| @media (prefers-reduced-motion: reduce) { | |
| .auth-waveform-bar, | |
| .auth-rec-dot, | |
| .auth-card { | |
| animation: none; | |
| } | |
| .auth-rec-indicator { | |
| background: var(--bg-primary); | |
| } | |
| } | |
| /* ββ Small screens: stricter rhythm ββ */ | |
| @media (max-width: 480px) { | |
| .auth-form-col { | |
| padding: var(--space-4); | |
| } | |
| .auth-form-meta { | |
| justify-content: center; | |
| gap: var(--space-4); | |
| } | |
| } | |
| `; | |