gni commited on
Commit
d4b6cc6
·
1 Parent(s): 15bdb22

Brand: Finalize Redac_Scan identity with Shield logo, custom favicon, and responsive HUD design.

Browse files
Files changed (5) hide show
  1. README.md +1 -1
  2. ui/index.html +1 -1
  3. ui/public/favicon.svg +3 -1
  4. ui/src/App.tsx +129 -121
  5. ui/src/index.css +62 -11
README.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🛡️ Redac
2
 
3
  A lightweight PII (Personally Identifiable Information) moderation MVP designed to sanitize sensitive data before it reaches LLM APIs.
4
 
 
1
+ # 🛡️ Redac_Scan
2
 
3
  A lightweight PII (Personally Identifiable Information) moderation MVP designed to sanitize sensitive data before it reaches LLM APIs.
4
 
ui/index.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>ui</title>
8
  </head>
9
  <body>
10
  <div id="root"></div>
 
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Redac_Scan</title>
8
  </head>
9
  <body>
10
  <div id="root"></div>
ui/public/favicon.svg CHANGED
ui/src/App.tsx CHANGED
@@ -3,7 +3,7 @@ import axios from 'axios';
3
  import {
4
  Shield, Eye, Lock, CheckCircle2, Copy,
5
  Database, Languages, FileText, Fingerprint, Zap, Activity,
6
- Palette, ChevronDown, Check, Sun, Moon, Sparkles
7
  } from 'lucide-react';
8
 
9
  interface EntityMeta {
@@ -25,12 +25,14 @@ type Theme = 'premium' | 'light' | 'dark';
25
 
26
  const EXAMPLES = [
27
  {
28
- label: "📄 FR - Contrat & PV",
 
29
  lang: "fr",
30
  text: `PROCÈS-VERBAL DE RÉUNION DE CHANTIER - RÉNOVATION COMPLEXE HÔTELIER\n\nDate : 20 Mars 2026\nLieu : 142 Avenue des Champs-Élysées, 75008 Paris.\n\nPRÉSENTS :\n- M. Alexandre de La Rochefoucauld (Directeur de projet, Groupe Immobilier "Lux-Horizon" - SIRET 321 654 987 00054).\n- Mme Valérie Marchand (Architecte, Cabinet "Marchand & Associés").\n- M. Thomas Dubois (Ingénieur sécurité, joignable au 06.45.12.89.33).\n\nORDRE DU JOUR ET DÉCISIONS :\n1. Validation des acomptes : La facture n°2026-04 d'un montant de 45 000€ a été réglée par virement sur le compte IBAN FR76 3000 1000 2000 3000 4000 500.`
31
  },
32
  {
33
- label: "📄 EN - Medical Record",
 
34
  lang: "en",
35
  text: `CLINICAL DISCHARGE SUMMARY\n\nPATIENT INFORMATION:\nName: Sarah-Jane Montgomery\nDOB: 12/05/1982\nAddress: 1244 North Oak Street, San Francisco, CA 94102\nEmergency Contact: Robert Montgomery (Husband) - Phone: (415) 555-0198\n\nADMISSION DIAGNOSIS:\nAcute respiratory distress. Patient was admitted to 'Green Valley General Hospital'. SSN: 123-45-6789.`
36
  }
@@ -76,176 +78,182 @@ function App() {
76
  try {
77
  const response = await axios.post(`${API_URL}/redact`, { text, language });
78
  setResult(response.data);
79
- } catch (err: any) { console.error(err); } finally { setLoading(false); }
 
80
  };
81
 
82
- const getScoreStyles = (score: number) => {
83
- if (score >= 90) return { text: 'text-rose-600', bg: 'bg-rose-500', border: 'border-rose-100', lightBg: theme === 'dark' ? 'bg-rose-900/20' : 'bg-rose-50' };
84
- if (score >= 70) return { text: 'text-amber-600', bg: 'bg-amber-500', border: 'border-amber-100', lightBg: theme === 'dark' ? 'bg-amber-900/20' : 'bg-amber-50' };
85
- return { text: 'text-emerald-600', bg: 'bg-emerald-500', border: 'border-emerald-100', lightBg: theme === 'dark' ? 'bg-emerald-900/20' : 'bg-emerald-50' };
86
- };
87
-
88
- const themeClasses = {
89
- premium: { body: 'bg-[#F9FAFB]', card: 'bg-white border-slate-200', text: 'text-slate-900', input: 'text-slate-700', dropdown: 'bg-white' },
90
- light: { body: 'bg-white', card: 'bg-white border-slate-950', text: 'text-black', input: 'text-black font-medium', dropdown: 'bg-white' },
91
- dark: { body: 'bg-[#020617]', card: 'bg-slate-900 border-slate-800', text: 'text-slate-100', input: 'text-slate-300', dropdown: 'bg-slate-900' }
92
- };
93
-
94
- const cur = themeClasses[theme];
95
-
96
  const themes = [
97
- { id: 'premium', label: 'Premium', icon: <Sparkles className="w-3.5 h-3.5" />, color: 'bg-blue-500' },
98
- { id: 'light', label: 'Light', icon: <Sun className="w-3.5 h-3.5" />, color: 'bg-slate-200' },
99
- { id: 'dark', label: 'Dark', icon: <Moon className="w-3.5 h-3.5" />, color: 'bg-slate-950' },
100
  ];
101
 
102
  const languages = [
103
- { id: 'auto', label: 'Auto' },
104
- { id: 'en', label: 'EN' },
105
- { id: 'fr', label: 'FR' },
106
  ];
107
 
108
  return (
109
- <div className={`min-h-screen ${cur.body} ${cur.text} font-sans selection:bg-blue-100 transition-colors duration-300`}>
110
- {theme === 'premium' && (
111
- <div className="fixed inset-0 overflow-hidden -z-10">
112
- <div className="absolute top-0 right-0 w-[300px] sm:w-[500px] h-[300px] sm:h-[500px] bg-blue-400/5 blur-[80px] sm:blur-[120px] rounded-full" />
113
- <div className="absolute bottom-0 left-0 w-[300px] sm:w-[500px] h-[300px] sm:h-[500px] bg-indigo-400/5 blur-[80px] sm:blur-[120px] rounded-full" />
114
- </div>
115
- )}
116
-
117
- <div className="max-w-7xl mx-auto px-4 sm:px-8 py-6 sm:py-12">
118
- <header className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-8 sm:mb-16 gap-6">
119
- <div className="flex items-center gap-3">
120
- <div className={`${theme === 'dark' ? 'bg-white text-black' : 'bg-black text-white'} p-2 rounded-lg shadow-sm`}><Shield className="w-5 h-5" /></div>
 
 
121
  <div>
122
- <h1 className={`text-xl font-bold tracking-tight ${cur.text}`}>Redac</h1>
123
- <div className="flex items-center gap-2 mt-0.5">
124
- <span className={`w-1.5 h-1.5 rounded-full ${apiStatus === 'online' ? 'bg-emerald-500 animate-pulse' : 'bg-rose-500'}`} />
125
- <span className="text-[10px] font-bold text-slate-400 uppercase tracking-widest">MVP Protection</span>
126
  </div>
127
  </div>
128
-
129
  </div>
130
-
131
- <div className="flex flex-wrap items-center gap-2 sm:gap-4 w-full sm:w-auto">
132
- {/* Custom Theme Dropdown */}
133
- <div className="relative flex-1 sm:flex-initial" ref={themeRef}>
134
  <button
135
- onClick={() => setIsThemeOpen(!isThemeOpen)}
136
- className={`w-full flex items-center justify-between gap-3 ${cur.card} px-3 py-2 sm:px-4 sm:py-2.5 rounded-xl border shadow-sm text-[10px] font-black uppercase tracking-widest text-slate-600`}
137
  >
138
- <div className="flex items-center gap-2">
139
- <Palette className="w-3.5 h-3.5" />
140
- <span className="hidden xs:inline">{themes.find(t => t.id === theme)?.label}</span>
141
- </div>
142
- <ChevronDown className={`w-3.5 h-3.5 transition-transform ${isThemeOpen ? 'rotate-180' : ''}`} />
143
  </button>
144
  {isThemeOpen && (
145
- <div className={`absolute right-0 mt-2 w-full sm:w-48 ${cur.dropdown} border ${theme === 'dark' ? 'border-slate-800' : 'border-slate-200'} rounded-2xl shadow-2xl z-50 overflow-hidden animate-in fade-in zoom-in-95 duration-200`}>
146
- <div className="p-2 space-y-1">
147
- {themes.map((t) => (
148
- <button key={t.id} onClick={() => { setTheme(t.id as Theme); setIsThemeOpen(false); }} className={`w-full flex items-center justify-between px-3 py-2.5 rounded-xl text-[10px] font-bold uppercase tracking-wider transition-colors ${theme === t.id ? (theme === 'dark' ? 'bg-slate-800 text-white' : 'bg-slate-50 text-black') : 'text-slate-500 hover:bg-slate-50'}`}>
149
- <div className="flex items-center gap-3"><div className={`w-2 h-2 rounded-full ${t.color}`} />{t.label}</div>
150
- {theme === t.id && <Check className="w-3 h-3 text-blue-500" />}
151
- </button>
152
- ))}
153
- </div>
154
  </div>
155
  )}
156
  </div>
157
 
158
- {/* Custom Language Dropdown */}
159
- <div className="relative flex-1 sm:flex-initial" ref={langRef}>
160
  <button
161
- onClick={() => setIsLangOpen(!isLangOpen)}
162
- className={`w-full flex items-center justify-between gap-3 ${cur.card} px-3 py-2 sm:px-4 sm:py-2.5 rounded-xl border shadow-sm text-[10px] font-black uppercase tracking-widest text-slate-600`}
163
  >
164
- <div className="flex items-center gap-2">
165
- <Languages className="w-3.5 h-3.5" />
166
- <span>{languages.find(l => l.id === language)?.label}</span>
167
- </div>
168
- <ChevronDown className={`w-3.5 h-3.5 transition-transform ${isLangOpen ? 'rotate-180' : ''}`} />
169
  </button>
170
  {isLangOpen && (
171
- <div className={`absolute right-0 mt-2 w-full sm:w-48 ${cur.dropdown} border ${theme === 'dark' ? 'border-slate-800' : 'border-slate-200'} rounded-2xl shadow-2xl z-50 overflow-hidden animate-in fade-in zoom-in-95 duration-200`}>
172
- <div className="p-2 space-y-1">
173
- {languages.map((l) => (
174
- <button key={l.id} onClick={() => { setLanguage(l.id); setIsLangOpen(false); }} className={`w-full flex items-center justify-between px-3 py-2.5 rounded-xl text-[10px] font-bold uppercase tracking-wider transition-colors ${language === l.id ? (theme === 'dark' ? 'bg-slate-800 text-white' : 'bg-slate-50 text-black') : 'text-slate-500 hover:bg-slate-50'}`}>
175
- {l.label}
176
- {language === l.id && <Check className="w-3 h-3 text-blue-500" />}
177
- </button>
178
- ))}
179
- </div>
180
  </div>
181
  )}
182
  </div>
183
  </div>
184
  </header>
185
 
186
- <div className="mb-8 sm:mb-12">
187
- <p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-4">Simulations</p>
188
- <div className="flex gap-2 sm:gap-4 overflow-x-auto pb-4 scrollbar-hide -mx-4 px-4 sm:mx-0 sm:px-0">
 
 
 
 
189
  {EXAMPLES.map((ex, i) => (
190
- <button key={i} onClick={() => { setText(ex.text); setLanguage(ex.lang); setResult(null); }} className={`px-4 py-2.5 sm:px-5 sm:py-3 ${cur.card} border rounded-xl text-[11px] sm:text-xs font-bold ${theme === 'light' ? 'hover:bg-black hover:text-white' : 'hover:border-blue-400'} transition-all whitespace-nowrap`}>{ex.label}</button>
 
 
 
 
 
 
 
191
  ))}
192
  </div>
193
  </div>
194
 
195
- <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-12">
196
- {/* Input Area */}
197
- <div className="lg:col-span-5 order-1">
198
- <div className={`${cur.card} rounded-2xl border p-5 sm:p-8 flex flex-col h-[400px] sm:h-[600px] lg:h-[650px] shadow-sm transition-colors`}>
199
- <div className="flex items-center justify-between mb-4 sm:mb-6 text-slate-400">
200
- <div className="flex items-center gap-2"><Database className="w-4 h-4" /><span className="text-[10px] font-bold uppercase tracking-widest">Source Document</span></div>
201
- <span className="text-[10px] font-mono">{text.length} chars</span>
 
 
 
202
  </div>
203
- <textarea className={`flex-grow w-full bg-transparent ${cur.input} text-sm sm:text-base font-medium leading-relaxed outline-none resize-none placeholder:text-slate-300`} placeholder="Paste sensitive data..." value={text} onChange={(e) => setText(e.target.value)} />
204
- <button onClick={() => handleRedact()} disabled={loading || !text.trim()} className={`mt-6 sm:mt-8 w-full py-3.5 sm:py-4 rounded-xl font-bold text-xs sm:text-sm transition-all flex items-center justify-center gap-3 ${loading || !text.trim() ? 'bg-slate-100 text-slate-400 cursor-not-allowed' : theme === 'light' ? 'bg-black text-white hover:bg-slate-800' : 'bg-blue-600 text-white hover:bg-blue-700 shadow-lg shadow-blue-500/20'}`}>
205
- {loading ? <div className="flex gap-1.5 items-center"><span className="w-1 h-1 bg-current rounded-full animate-bounce [animation-delay:-0.3s]"></span><span className="w-1 h-1 bg-current rounded-full animate-bounce [animation-delay:-0.15s]"></span><span className="w-1 h-1 bg-current rounded-full animate-bounce"></span></div> : <><Zap className={`w-4 h-4 ${theme === 'light' ? 'fill-white' : 'fill-current'}`} /><span>Run Sanitization</span></>}
 
 
 
 
 
 
 
 
 
206
  </button>
207
  </div>
208
  </div>
209
 
210
- {/* Results Area */}
211
- <div className="lg:col-span-7 space-y-6 sm:space-y-8 order-2">
212
- <div className={`${theme === 'dark' ? 'bg-black border-slate-800' : 'bg-slate-900 border-transparent'} rounded-2xl p-6 sm:p-10 h-[350px] sm:h-[450px] flex flex-col shadow-xl relative border transition-colors`}>
213
- <div className="flex items-center justify-between mb-6 sm:mb-8">
214
- <div className="flex items-center gap-3"><div className="w-1.5 h-1.5 bg-emerald-400 rounded-full" /><span className="text-[10px] font-bold uppercase tracking-widest text-emerald-400/70">Secure Output</span></div>
215
- {result && <button onClick={() => {navigator.clipboard.writeText(result.redacted_text); setCopied(true); setTimeout(()=>setCopied(false), 2000)}} className="text-[10px] font-bold uppercase text-white/50 hover:text-white transition-colors">{copied ? 'Copié' : 'Copy'}</button>}
 
 
 
 
 
 
 
216
  </div>
217
- <div className="flex-grow font-mono text-xs sm:text-[13px] leading-relaxed text-emerald-400/90 whitespace-pre-wrap overflow-y-auto custom-scrollbar">
218
- {!result ? <div className="h-full flex items-center justify-center text-slate-700 italic text-center px-4">No output generated. Run sanitization to see results.</div> : result.redacted_text}
219
  </div>
220
  </div>
221
 
222
- {/* Analysis Grid */}
223
  {result && (
224
- <div className="grid grid-cols-1 xs:grid-cols-2 gap-3 sm:gap-4">
225
- <div className="xs:col-span-2 flex items-center gap-2 mb-1 sm:mb-2">
226
- <Fingerprint className="w-4 h-4 text-slate-400" />
227
- <span className="text-[10px] font-bold uppercase tracking-widest text-slate-400">Risk Analysis</span>
228
  </div>
229
- {result.entities.map((ent, idx) => {
230
- const styles = getScoreStyles(ent.score);
231
- return (
232
- <div key={idx} className={`${cur.card} p-3 sm:p-4 rounded-xl border flex items-center justify-between transition-all hover:shadow-sm`}>
233
- <div className="overflow-hidden mr-2">
234
- <p className={`text-[8px] sm:text-[9px] font-black uppercase mb-0.5 ${styles.text}`}>{ent.type}</p>
235
- <p className={`text-[11px] sm:text-xs font-bold line-clamp-1 italic ${cur.text}`}>"{ent.text}"</p>
236
  </div>
237
  <div className="text-right flex-shrink-0">
238
- <div className="flex items-center justify-end gap-1.5">
239
- <Activity className={`w-2.5 h-2.5 sm:w-3 h-3 ${styles.text}`} />
240
- <p className={`text-[10px] sm:text-[11px] font-black ${styles.text}`}>{ent.score}%</p>
241
- </div>
242
- <div className={`w-10 sm:w-12 h-1 ${theme === 'dark' ? 'bg-slate-800' : 'bg-slate-100'} rounded-full mt-1.5 overflow-hidden ml-auto`}>
243
- <div className={`h-full ${styles.bg}`} style={{ width: `${ent.score}%` }} />
244
  </div>
245
  </div>
246
  </div>
247
- );
248
- })}
249
  </div>
250
  )}
251
  </div>
 
3
  import {
4
  Shield, Eye, Lock, CheckCircle2, Copy,
5
  Database, Languages, FileText, Fingerprint, Zap, Activity,
6
+ Palette, ChevronDown, Check, Cpu, Radio, Target, Terminal
7
  } from 'lucide-react';
8
 
9
  interface EntityMeta {
 
25
 
26
  const EXAMPLES = [
27
  {
28
+ id: "PROTO-01",
29
+ label: "FR - Procès Verbal",
30
  lang: "fr",
31
  text: `PROCÈS-VERBAL DE RÉUNION DE CHANTIER - RÉNOVATION COMPLEXE HÔTELIER\n\nDate : 20 Mars 2026\nLieu : 142 Avenue des Champs-Élysées, 75008 Paris.\n\nPRÉSENTS :\n- M. Alexandre de La Rochefoucauld (Directeur de projet, Groupe Immobilier "Lux-Horizon" - SIRET 321 654 987 00054).\n- Mme Valérie Marchand (Architecte, Cabinet "Marchand & Associés").\n- M. Thomas Dubois (Ingénieur sécurité, joignable au 06.45.12.89.33).\n\nORDRE DU JOUR ET DÉCISIONS :\n1. Validation des acomptes : La facture n°2026-04 d'un montant de 45 000€ a été réglée par virement sur le compte IBAN FR76 3000 1000 2000 3000 4000 500.`
32
  },
33
  {
34
+ id: "DATA-02",
35
+ label: "EN - Medical Summary",
36
  lang: "en",
37
  text: `CLINICAL DISCHARGE SUMMARY\n\nPATIENT INFORMATION:\nName: Sarah-Jane Montgomery\nDOB: 12/05/1982\nAddress: 1244 North Oak Street, San Francisco, CA 94102\nEmergency Contact: Robert Montgomery (Husband) - Phone: (415) 555-0198\n\nADMISSION DIAGNOSIS:\nAcute respiratory distress. Patient was admitted to 'Green Valley General Hospital'. SSN: 123-45-6789.`
38
  }
 
78
  try {
79
  const response = await axios.post(`${API_URL}/redact`, { text, language });
80
  setResult(response.data);
81
+ } catch (err: any) { console.error(err); }
82
+ finally { setTimeout(() => setLoading(false), 1000); }
83
  };
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  const themes = [
86
+ { id: 'premium', label: 'Cyber Premium', color: 'bg-cyan-500' },
87
+ { id: 'light', label: 'Clinical Light', color: 'bg-slate-300' },
88
+ { id: 'dark', label: 'Void Dark', color: 'bg-slate-950' },
89
  ];
90
 
91
  const languages = [
92
+ { id: 'auto', label: 'AUTO-SCAN' },
93
+ { id: 'en', label: 'ENGLISH' },
94
+ { id: 'fr', label: 'FRENCH' },
95
  ];
96
 
97
  return (
98
+ <div className={`min-h-screen relative overflow-x-hidden transition-all duration-500 font-mono ${theme === 'light' ? 'bg-slate-50 text-slate-900' : 'bg-[#050a15] text-cyan-400'}`}>
99
+
100
+ {/* BACKGROUND ELEMENTS */}
101
+ <div className="absolute inset-0 bg-grid pointer-events-none opacity-50" />
102
+
103
+ <div className="relative z-10 max-w-[1400px] mx-auto px-4 sm:px-10 py-6 sm:py-10">
104
+
105
+ {/* HEADER */}
106
+ <header className="flex flex-col md:flex-row items-center justify-between mb-8 border-b border-cyan-500/20 pb-8 gap-6">
107
+ <div className="flex items-center gap-5">
108
+ <div className="relative">
109
+ <div className="absolute inset-0 bg-cyan-500 blur-lg opacity-20" />
110
+ <div className={`relative border-2 ${theme === 'light' ? 'border-slate-900' : 'border-cyan-500'} p-2 rounded-lg`}><Shield className="w-6 h-6" /></div>
111
+ </div>
112
  <div>
113
+ <h1 className={`text-2xl font-black italic tracking-tighter uppercase leading-none ${theme === 'light' ? 'text-slate-900' : 'text-white'}`}>REDAC<span className="text-cyan-500">_SCAN</span></h1>
114
+ <div className="flex items-center gap-2 mt-1">
115
+ <Radio className={`w-3 h-3 ${apiStatus === 'online' ? 'text-emerald-400 animate-pulse' : 'text-rose-500'}`} />
116
+ <span className="text-[9px] font-bold tracking-[0.2em] uppercase opacity-60">LINK_STATUS: {apiStatus.toUpperCase()}</span>
117
  </div>
118
  </div>
 
119
  </div>
120
+
121
+ <div className="flex items-center gap-3">
122
+ {/* Theme Dropdown */}
123
+ <div className="relative" ref={themeRef}>
124
  <button
125
+ onClick={() => setIsThemeOpen(!isThemeOpen)}
126
+ className={`flex items-center gap-3 px-4 py-2 border ${theme === 'light' ? 'border-slate-200 bg-white shadow-sm' : 'border-cyan-500/30 bg-slate-900/50'} rounded-xl text-[10px] font-bold tracking-widest uppercase transition-all hover:border-cyan-500`}
127
  >
128
+ <Palette className="w-3.5 h-3.5" />
129
+ <span>{themes.find(t => t.id === theme)?.label}</span>
130
+ <ChevronDown className={`w-3 h-3 transition-transform ${isThemeOpen ? 'rotate-180' : ''}`} />
 
 
131
  </button>
132
  {isThemeOpen && (
133
+ <div className="absolute right-0 mt-3 w-56 bg-slate-900 border border-cyan-500/50 rounded-xl shadow-2xl z-[100] overflow-hidden animate-in fade-in zoom-in-95 duration-200">
134
+ {themes.map((t) => (
135
+ <button key={t.id} onClick={() => { setTheme(t.id as Theme); setIsThemeOpen(false); }} className={`w-full text-left px-4 py-3 text-[10px] font-bold uppercase tracking-widest flex items-center justify-between hover:bg-cyan-500/20 ${theme === t.id ? 'text-cyan-400 bg-cyan-500/5' : 'text-slate-400'}`}>
136
+ <div className="flex items-center gap-3"><div className={`w-2 h-2 rounded-full ${t.color}`} />{t.label}</div>
137
+ {theme === t.id && <Check className="w-3 h-3" />}
138
+ </button>
139
+ ))}
 
 
140
  </div>
141
  )}
142
  </div>
143
 
144
+ {/* Lang Dropdown */}
145
+ <div className="relative" ref={langRef}>
146
  <button
147
+ onClick={() => setIsLangOpen(!isLangOpen)}
148
+ className={`flex items-center gap-3 px-4 py-2 border ${theme === 'light' ? 'border-slate-200 bg-white shadow-sm' : 'border-cyan-500/30 bg-slate-900/50'} rounded-xl text-[10px] font-bold tracking-widest uppercase transition-all hover:border-cyan-500`}
149
  >
150
+ <Languages className="w-3.5 h-3.5" />
151
+ <span>{languages.find(l => l.id === language)?.label}</span>
152
+ <ChevronDown className={`w-3 h-3 transition-transform ${isLangOpen ? 'rotate-180' : ''}`} />
 
 
153
  </button>
154
  {isLangOpen && (
155
+ <div className="absolute right-0 mt-3 w-56 bg-slate-900 border border-cyan-500/50 rounded-xl shadow-2xl z-[100] overflow-hidden animate-in fade-in zoom-in-95 duration-200">
156
+ {languages.map((l) => (
157
+ <button key={l.id} onClick={() => { setLanguage(l.id); setIsLangOpen(false); }} className={`w-full text-left px-4 py-3 text-[10px] font-bold uppercase tracking-widest flex items-center justify-between hover:bg-cyan-500/20 ${language === l.id ? 'text-cyan-400 bg-cyan-500/5' : 'text-slate-400'}`}>
158
+ {l.label}
159
+ {language === l.id && <Check className="w-3 h-3" />}
160
+ </button>
161
+ ))}
 
 
162
  </div>
163
  )}
164
  </div>
165
  </div>
166
  </header>
167
 
168
+ {/* PRESETS BAR (TOP) */}
169
+ <div className="mb-10">
170
+ <div className="flex items-center gap-3 mb-4 opacity-60">
171
+ <Target className="w-3.5 h-3.5" />
172
+ <span className="text-[10px] font-bold tracking-[0.3em] uppercase">Security Protocols / Presets</span>
173
+ </div>
174
+ <div className="flex flex-wrap gap-3">
175
  {EXAMPLES.map((ex, i) => (
176
+ <button
177
+ key={i}
178
+ onClick={() => { setText(ex.text); setLanguage(ex.lang); setResult(null); }}
179
+ className={`flex items-center gap-4 p-3 pr-6 border ${theme === 'light' ? 'bg-white border-slate-200' : 'bg-cyan-500/5 border-cyan-500/20'} hover:border-cyan-500 rounded-xl transition-all group`}
180
+ >
181
+ <div className={`w-8 h-8 rounded-lg flex items-center justify-center text-[10px] font-bold ${theme === 'light' ? 'bg-slate-100' : 'bg-cyan-500/10 text-cyan-500'}`}>{ex.id}</div>
182
+ <span className={`text-[11px] font-bold tracking-tight ${theme === 'light' ? 'text-slate-700' : 'text-slate-200'} group-hover:text-cyan-500`}>{ex.label}</span>
183
+ </button>
184
  ))}
185
  </div>
186
  </div>
187
 
188
+ {/* WORKSPACE GRIDS */}
189
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 sm:gap-12">
190
+
191
+ {/* INPUT HUB */}
192
+ <div className="relative group">
193
+ <div className={`glass-panel rounded-3xl p-6 sm:p-8 border-2 ${loading ? 'border-cyan-500 shadow-[0_0_30px_rgba(0,242,255,0.1)]' : 'border-white/5'} min-h-[550px] flex flex-col relative overflow-hidden transition-all duration-500`}>
194
+ <div className={`absolute top-0 left-0 w-full scanner-line ${loading ? 'opacity-100' : 'opacity-0'}`} />
195
+ <div className="flex items-center justify-between mb-8 opacity-60">
196
+ <div className="flex items-center gap-3"><Database className="w-4 h-4" /><span className="text-[10px] font-bold tracking-[0.2em] uppercase">Data Ingest</span></div>
197
+ <div className="text-[9px] font-bold tracking-widest">{text.length} CHARS / {text.length * 8} BITS</div>
198
  </div>
199
+ <textarea
200
+ className={`flex-grow w-full bg-transparent border-none outline-none ${theme === 'light' ? 'text-slate-800' : 'text-white'} text-sm leading-relaxed placeholder:text-slate-700 resize-none font-medium custom-scrollbar`}
201
+ placeholder="INPUT DATA STREAM FOR ANALYSIS..."
202
+ value={text}
203
+ onChange={(e) => setText(e.target.value)}
204
+ />
205
+ <button
206
+ onClick={handleRedact}
207
+ disabled={loading || !text.trim()}
208
+ className={`mt-8 w-full py-5 rounded-2xl font-black text-xs tracking-[0.4em] uppercase transition-all ${loading || !text.trim() ? 'bg-slate-800 text-slate-600' : 'bg-cyan-500 text-black hover:shadow-[0_0_30px_rgba(0,242,255,0.3)] active:scale-95'}`}
209
+ >
210
+ {loading ? 'PROCESSING...' : 'START_SCAN'}
211
  </button>
212
  </div>
213
  </div>
214
 
215
+ {/* OUTPUT & RADAR */}
216
+ <div className="space-y-8">
217
+ <div className={`rounded-3xl p-6 sm:p-8 border ${theme === 'light' ? 'bg-white border-slate-200' : 'bg-black/60 border-cyan-500/20'} min-h-[350px] flex flex-col relative shadow-xl`}>
218
+ <div className="flex items-center justify-between mb-8">
219
+ <div className="flex items-center gap-3">
220
+ <div className="w-1.5 h-1.5 bg-emerald-400 rounded-full shadow-[0_0_8px_#10b981]" />
221
+ <span className="text-[10px] font-bold tracking-[0.2em] uppercase opacity-60">Secure Stream</span>
222
+ </div>
223
+ {result && (
224
+ <button onClick={() => {navigator.clipboard.writeText(result.redacted_text); setCopied(true); setTimeout(()=>setCopied(false), 2000)}} className="text-[9px] font-bold uppercase tracking-widest opacity-40 hover:opacity-100 transition-opacity">
225
+ {copied ? 'TRANSFERRED' : '[ COPY_STREAM ]'}
226
+ </button>
227
+ )}
228
  </div>
229
+ <div className={`flex-grow ${theme === 'light' ? 'text-slate-700' : 'text-cyan-400'} text-[13px] leading-relaxed whitespace-pre-wrap overflow-y-auto custom-scrollbar italic font-medium`}>
230
+ {!result ? <div className="h-full flex flex-col items-center justify-center opacity-10"><Lock className="w-10 h-10 mb-4" /><p className="text-[9px] tracking-[0.2em]">LINK_STBY</p></div> : result.redacted_text}
231
  </div>
232
  </div>
233
 
234
+ {/* MATRIX ANALYSIS */}
235
  {result && (
236
+ <div className="space-y-4">
237
+ <div className="flex items-center gap-3 px-2 opacity-60">
238
+ <Fingerprint className="w-4 h-4" />
239
+ <span className="text-[10px] font-bold tracking-[0.2em] uppercase">Threat Identification Matrix</span>
240
  </div>
241
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
242
+ {result.entities.map((ent, idx) => (
243
+ <div key={idx} className={`p-4 rounded-xl border ${theme === 'light' ? 'bg-white border-slate-100' : 'bg-cyan-500/5 border-cyan-500/10'} flex items-center justify-between`}>
244
+ <div>
245
+ <p className="text-[8px] font-black text-cyan-500/60 uppercase mb-1">{ent.type}</p>
246
+ <p className={`text-[11px] font-bold truncate max-w-[100px] ${theme === 'light' ? 'text-slate-800' : 'text-white'}`}>"{ent.text}"</p>
 
247
  </div>
248
  <div className="text-right flex-shrink-0">
249
+ <div className="flex items-center gap-2 text-cyan-500 font-black text-xs">{ent.score}%</div>
250
+ <div className="w-10 h-1 bg-slate-800/50 rounded-full mt-1.5 overflow-hidden">
251
+ <div className="h-full bg-cyan-500" style={{ width: `${ent.score}%` }} />
 
 
 
252
  </div>
253
  </div>
254
  </div>
255
+ ))}
256
+ </div>
257
  </div>
258
  )}
259
  </div>
ui/src/index.css CHANGED
@@ -1,24 +1,75 @@
1
  @import "tailwindcss";
2
 
3
  @theme {
4
- --color-brand: #3b82f6;
5
- --color-brand-dark: #1d4ed8;
6
- --radius-xl: 1rem;
7
- --radius-2xl: 1.5rem;
8
  }
9
 
10
- body {
11
- @apply bg-slate-50 text-slate-900 antialiased;
 
 
12
  }
13
 
14
- ::-webkit-scrollbar {
15
- width: 8px;
 
 
 
 
 
16
  }
17
 
18
- ::-webkit-scrollbar-track {
19
- @apply bg-transparent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  ::-webkit-scrollbar-thumb {
23
- @apply bg-slate-200 rounded-full hover:bg-slate-300 transition-colors;
 
 
24
  }
 
1
  @import "tailwindcss";
2
 
3
  @theme {
4
+ --color-cyber-blue: #00f2ff;
5
+ --color-cyber-pink: #ff00e5;
6
+ --color-cyber-dark: #050a15;
 
7
  }
8
 
9
+ @layer base {
10
+ body {
11
+ @apply bg-cyber-dark text-slate-200 antialiased overflow-x-hidden;
12
+ }
13
  }
14
 
15
+ /* Grille futuriste en arrière-plan */
16
+ .bg-grid {
17
+ background-image:
18
+ linear-gradient(to right, rgba(0, 242, 255, 0.05) 1px, transparent 1px),
19
+ linear-gradient(to bottom, rgba(0, 242, 255, 0.05) 1px, transparent 1px);
20
+ background-size: 40px 40px;
21
+ mask-image: radial-gradient(circle at center, black, transparent 80%);
22
  }
23
 
24
+ /* Animation de scan laser */
25
+ @keyframes scan {
26
+ 0% { transform: translateY(-100%); opacity: 0; }
27
+ 50% { opacity: 1; }
28
+ 100% { transform: translateY(1000%); opacity: 0; }
29
+ }
30
+
31
+ .scanner-line {
32
+ height: 2px;
33
+ background: linear-gradient(90deg, transparent, var(--color-cyber-blue), transparent);
34
+ box-shadow: 0 0 15px var(--color-cyber-blue);
35
+ animation: scan 3s linear infinite;
36
+ display: none;
37
+ }
38
+
39
+ .is-scanning .scanner-line {
40
+ display: block;
41
+ }
42
+
43
+ /* Glassmorphism */
44
+ .glass-panel {
45
+ background: rgba(15, 23, 42, 0.6);
46
+ backdrop-filter: blur(12px);
47
+ border: 1px solid rgba(255, 255, 255, 0.1);
48
  }
49
 
50
+ .neon-border {
51
+ position: relative;
52
+ }
53
+
54
+ .neon-border::after {
55
+ content: '';
56
+ position: absolute;
57
+ inset: -1px;
58
+ background: linear-gradient(45deg, var(--color-cyber-blue), transparent, var(--color-cyber-pink));
59
+ z-index: -1;
60
+ border-radius: inherit;
61
+ opacity: 0.3;
62
+ }
63
+
64
+ /* Custom Scrollbar futuriste */
65
+ ::-webkit-scrollbar {
66
+ width: 4px;
67
+ }
68
+ ::-webkit-scrollbar-track {
69
+ background: transparent;
70
+ }
71
  ::-webkit-scrollbar-thumb {
72
+ background: var(--color-cyber-blue);
73
+ border-radius: 10px;
74
+ box-shadow: 0 0 10px var(--color-cyber-blue);
75
  }