11<!DOCTYPE html>
22< html lang ="en ">
33< head >
4- < meta charset ="UTF-8 ">
5- < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
6- < title > DocSense</ title >
4+ < meta charset ="UTF-8 " />
5+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
6+ < meta name ="description " content ="DocSense — fast local document search using TF-IDF and BM25 ranking. " />
7+ < title > DocSense</ title >
8+ < link rel ="preconnect " href ="https://fonts.googleapis.com " />
9+ < link rel ="preconnect " href ="https://fonts.gstatic.com " crossorigin />
10+ < link href ="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap " rel ="stylesheet " />
11+ < style >
12+ * , * ::before , * ::after { box-sizing : border-box; margin : 0 ; padding : 0 ; }
13+
14+ : root {
15+ --bg : # 0c0c10 ;
16+ --surface : # 13131a ;
17+ --border : rgba (255 , 255 , 255 , 0.07 );
18+ --border-focus : rgba (139 , 92 , 246 , 0.5 );
19+ --accent : # 8b5cf6 ;
20+ --accent-dim : rgba (139 , 92 , 246 , 0.15 );
21+ --text : # e2e2e9 ;
22+ --muted : # 6b6b7a ;
23+ --danger : # f87171 ;
24+ --radius : 14px ;
25+ --font : 'Inter' , system-ui, sans-serif;
26+ }
27+
28+ html , body {
29+ height : 100% ;
30+ background : var (--bg );
31+ color : var (--text );
32+ font-family : var (--font );
33+ font-size : 15px ;
34+ line-height : 1.5 ;
35+ -webkit-font-smoothing : antialiased;
36+ }
37+
38+ /* ── Layout ── */
39+ .page {
40+ min-height : 100vh ;
41+ display : grid;
42+ grid-template-rows : 1fr auto;
43+ padding : 0 24px ;
44+ }
45+
46+ .main {
47+ display : flex;
48+ flex-direction : column;
49+ align-items : center;
50+ padding-top : min (18vh , 140px );
51+ width : 100% ;
52+ }
53+
54+ /* ── Wordmark ── */
55+ .wordmark {
56+ display : flex;
57+ align-items : center;
58+ gap : 10px ;
59+ margin-bottom : 40px ;
60+ }
61+
62+ .wordmark-icon {
63+ width : 36px ;
64+ height : 36px ;
65+ background : linear-gradient (135deg , # 8b5cf6, # 6366f1 );
66+ border-radius : 9px ;
67+ display : grid;
68+ place-items : center;
69+ flex-shrink : 0 ;
70+ }
71+
72+ .wordmark-icon svg { display : block; }
73+
74+ .wordmark-name {
75+ font-size : 22px ;
76+ font-weight : 600 ;
77+ letter-spacing : -0.5px ;
78+ color : var (--text );
79+ }
80+
81+ .wordmark-tag {
82+ font-size : 12px ;
83+ font-weight : 400 ;
84+ color : var (--muted );
85+ letter-spacing : 0.02em ;
86+ }
87+
88+ /* ── Search box ── */
89+ .search-wrap {
90+ position : relative;
91+ width : 100% ;
92+ max-width : 640px ;
93+ }
94+
95+ .search-icon {
96+ position : absolute;
97+ left : 18px ;
98+ top : 50% ;
99+ transform : translateY (-50% );
100+ color : var (--muted );
101+ pointer-events : none;
102+ transition : color 0.2s ;
103+ }
104+
105+ # query {
106+ width : 100% ;
107+ height : 56px ;
108+ padding : 0 52px 0 50px ;
109+ background : var (--surface );
110+ border : 1px solid var (--border );
111+ border-radius : var (--radius );
112+ color : var (--text );
113+ font-family : var (--font );
114+ font-size : 15px ;
115+ font-weight : 400 ;
116+ outline : none;
117+ transition : border-color 0.2s , box-shadow 0.2s ;
118+ -webkit-appearance : none;
119+ }
120+
121+ # query ::placeholder { color : var (--muted ); }
122+
123+ # query : focus {
124+ border-color : var (--border-focus );
125+ box-shadow : 0 0 0 3px rgba (139 , 92 , 246 , 0.12 ), 0 1px 2px rgba (0 , 0 , 0 , 0.4 );
126+ }
127+
128+ # query : focus + .search-icon-right ,
129+ # query : focus ~ .search-icon { color : var (--accent ); }
130+
131+ .search-hint {
132+ position : absolute;
133+ right : 16px ;
134+ top : 50% ;
135+ transform : translateY (-50% );
136+ display : flex;
137+ align-items : center;
138+ gap : 4px ;
139+ pointer-events : none;
140+ opacity : 1 ;
141+ transition : opacity 0.15s ;
142+ }
143+
144+ .search-hint .hidden { opacity : 0 ; }
145+
146+ .kbd {
147+ display : inline-flex;
148+ align-items : center;
149+ justify-content : center;
150+ height : 22px ;
151+ padding : 0 6px ;
152+ border : 1px solid var (--border );
153+ border-radius : 5px ;
154+ background : rgba (255 , 255 , 255 , 0.04 );
155+ color : var (--muted );
156+ font-family : var (--font );
157+ font-size : 11px ;
158+ font-weight : 500 ;
159+ line-height : 1 ;
160+ }
161+
162+ /* ── Spinner ── */
163+ .spinner {
164+ position : absolute;
165+ right : 18px ;
166+ top : 50% ;
167+ transform : translateY (-50% );
168+ width : 18px ;
169+ height : 18px ;
170+ border : 2px solid var (--border );
171+ border-top-color : var (--accent );
172+ border-radius : 50% ;
173+ display : none;
174+ animation : spin 0.65s linear infinite;
175+ }
176+ .spinner .active { display : block; }
177+ @keyframes spin { to { transform : translateY (-50% ) rotate (360deg ); } }
178+
179+ /* ── Results ── */
180+ # results {
181+ width : 100% ;
182+ max-width : 640px ;
183+ margin-top : 12px ;
184+ }
185+
186+ .results-header {
187+ display : flex;
188+ align-items : center;
189+ justify-content : space-between;
190+ padding : 10px 4px 8px ;
191+ font-size : 12px ;
192+ color : var (--muted );
193+ font-weight : 500 ;
194+ letter-spacing : 0.03em ;
195+ }
196+
197+ .result-list { display : flex; flex-direction : column; gap : 2px ; }
198+
199+ .result-item {
200+ display : flex;
201+ align-items : center;
202+ gap : 12px ;
203+ padding : 11px 14px ;
204+ background : var (--surface );
205+ border : 1px solid var (--border );
206+ border-radius : 10px ;
207+ text-decoration : none;
208+ color : var (--text );
209+ transition : background 0.15s , border-color 0.15s , transform 0.1s ;
210+ animation : fadeUp 0.18s ease both;
211+ }
212+
213+ .result-item : hover {
214+ background : rgba (139 , 92 , 246 , 0.07 );
215+ border-color : rgba (139 , 92 , 246 , 0.25 );
216+ transform : translateY (-1px );
217+ }
218+
219+ @keyframes fadeUp {
220+ from { opacity : 0 ; transform : translateY (6px ); }
221+ to { opacity : 1 ; transform : translateY (0 ); }
222+ }
223+
224+ .result-item : nth-child (2 ) { animation-delay : 0.03s ; }
225+ .result-item : nth-child (3 ) { animation-delay : 0.06s ; }
226+ .result-item : nth-child (4 ) { animation-delay : 0.09s ; }
227+ .result-item : nth-child (5 ) { animation-delay : 0.12s ; }
228+ .result-item : nth-child (6 ) { animation-delay : 0.15s ; }
229+ .result-item : nth-child (7 ) { animation-delay : 0.18s ; }
230+ .result-item : nth-child (8 ) { animation-delay : 0.21s ; }
231+ .result-item : nth-child (9 ) { animation-delay : 0.24s ; }
232+ .result-item : nth-child (10 ) { animation-delay : 0.27s ; }
233+
234+ .ext-badge {
235+ flex-shrink : 0 ;
236+ width : 36px ;
237+ height : 36px ;
238+ border-radius : 8px ;
239+ display : grid;
240+ place-items : center;
241+ font-size : 10px ;
242+ font-weight : 600 ;
243+ letter-spacing : 0.04em ;
244+ text-transform : uppercase;
245+ }
246+
247+ .ext-xml , .ext-xhtml { background : rgba (251 , 146 , 60 , 0.12 ); color : # fb923c ; }
248+ .ext-pdf { background : rgba (248 , 113 , 113 , 0.12 ); color : # f87171 ; }
249+ .ext-txt , .ext-md { background : rgba (139 , 92 , 246 , 0.12 ); color : # a78bfa ; }
250+ .ext-default { background : rgba (107 , 107 , 122 , 0.15 ); color : var (--muted ); }
251+
252+ .result-body { flex : 1 ; min-width : 0 ; }
253+
254+ .result-filename {
255+ font-size : 14px ;
256+ font-weight : 500 ;
257+ color : var (--text );
258+ white-space : nowrap;
259+ overflow : hidden;
260+ text-overflow : ellipsis;
261+ }
262+
263+ .result-path {
264+ font-size : 12px ;
265+ color : var (--muted );
266+ white-space : nowrap;
267+ overflow : hidden;
268+ text-overflow : ellipsis;
269+ margin-top : 1px ;
270+ }
271+
272+ .result-rank {
273+ flex-shrink : 0 ;
274+ font-size : 12px ;
275+ color : var (--muted );
276+ font-variant-numeric : tabular-nums;
277+ font-weight : 500 ;
278+ }
279+
280+ /* ── Empty / Error states ── */
281+ .state-msg {
282+ display : flex;
283+ flex-direction : column;
284+ align-items : center;
285+ gap : 8px ;
286+ padding : 40px 0 20px ;
287+ color : var (--muted );
288+ font-size : 14px ;
289+ text-align : center;
290+ animation : fadeUp 0.2s ease;
291+ }
292+
293+ .state-msg svg { opacity : 0.4 ; }
294+ .state-msg .error { color : var (--danger ); }
295+
296+ /* ── Stats footer ── */
297+ footer {
298+ padding : 20px 0 ;
299+ text-align : center;
300+ font-size : 12px ;
301+ color : var (--muted );
302+ display : flex;
303+ align-items : center;
304+ justify-content : center;
305+ gap : 16px ;
306+ }
307+
308+ .stat-item { display : flex; align-items : center; gap : 5px ; }
309+ .stat-dot {
310+ width : 6px ; height : 6px ;
311+ border-radius : 50% ;
312+ background : var (--accent );
313+ opacity : 0.6 ;
314+ }
315+ </ style >
7316</ head >
8317< body >
9- < center >
10- < h1 > < b > DocSense</ b > </ h1 >
11- < h2 > Query: </ h2 >
12- < input type ="text " id ="query "/>
13- < div id ="results "> </ div >
14- </ center >
318+ < div class ="page ">
319+ < div class ="main ">
320+ < div class ="wordmark ">
321+ < div class ="wordmark-icon ">
322+ <!-- doc + magnifier icon -->
323+ < svg width ="20 " height ="20 " viewBox ="0 0 20 20 " fill ="none " xmlns ="http://www.w3.org/2000/svg ">
324+ < path d ="M5 3h7l4 4v10a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1Z " stroke ="rgba(255,255,255,0.9) " stroke-width ="1.4 " stroke-linejoin ="round "/>
325+ < path d ="M12 3v4h4 " stroke ="rgba(255,255,255,0.9) " stroke-width ="1.4 " stroke-linecap ="round " stroke-linejoin ="round "/>
326+ < circle cx ="9.5 " cy ="11.5 " r ="2 " stroke ="rgba(255,255,255,0.9) " stroke-width ="1.4 "/>
327+ < path d ="M11 13l1.5 1.5 " stroke ="rgba(255,255,255,0.9) " stroke-width ="1.4 " stroke-linecap ="round "/>
328+ </ svg >
329+ </ div >
330+ < div >
331+ < div class ="wordmark-name "> DocSense</ div >
332+ < div class ="wordmark-tag "> Local document search</ div >
333+ </ div >
334+ </ div >
335+
336+ < div class ="search-wrap ">
337+ <!-- search icon -->
338+ < svg class ="search-icon " width ="18 " height ="18 " viewBox ="0 0 18 18 " fill ="none ">
339+ < circle cx ="8 " cy ="8 " r ="5.5 " stroke ="currentColor " stroke-width ="1.5 "/>
340+ < path d ="M12.5 12.5L16 16 " stroke ="currentColor " stroke-width ="1.5 " stroke-linecap ="round "/>
341+ </ svg >
342+
343+ < input
344+ id ="query "
345+ type ="text "
346+ placeholder ="Search your documents… "
347+ autocomplete ="off "
348+ spellcheck ="false "
349+ aria-label ="Search query "
350+ />
351+
352+ < div class ="search-hint " id ="hint ">
353+ < span class ="kbd "> ↵</ span >
354+ </ div >
355+ < div class ="spinner " id ="spinner "> </ div >
356+ </ div >
357+
358+ < div id ="results " aria-live ="polite " aria-label ="Search results "> </ div >
359+ </ div >
360+
361+ < footer id ="stats ">
362+ < span class ="stat-item ">
363+ < span class ="stat-dot "> </ span >
364+ < span id ="stat-docs "> —</ span > documents indexed
365+ </ span >
366+ < span class ="stat-item ">
367+ < span class ="stat-dot "> </ span >
368+ < span id ="stat-terms "> —</ span > unique terms
369+ </ span >
370+ </ footer >
371+ </ div >
15372
373+ < script src ="index.js "> </ script >
16374</ body >
17- < script src ="index.js "> </ script >
18375</ html >
0 commit comments