pdf-smith 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,250 @@
1
+ interface IconProps {
2
+ size?: number;
3
+ style?: React.CSSProperties;
4
+ }
5
+
6
+ export function AnvilIcon({ size = 24, style }: IconProps) {
7
+ return (
8
+ <svg
9
+ width={size}
10
+ height={size}
11
+ viewBox="0 0 32 32"
12
+ fill="none"
13
+ style={style}
14
+ aria-hidden="true"
15
+ >
16
+ <rect x="4" y="6" width="24" height="5" rx="1.5" fill="#C2410C" />
17
+ <path d="M2 24h28v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-3z" fill="#FAF9F7" />
18
+ <path d="M8 11h16v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2z" fill="#57534E" />
19
+ <rect x="10" y="14" width="12" height="4" rx="1" fill="#78716C" />
20
+ <rect x="6" y="18" width="20" height="3" rx="1" fill="#57534E" />
21
+ <rect x="4" y="21" width="24" height="3" rx="1" fill="#44403C" />
22
+ </svg>
23
+ );
24
+ }
25
+
26
+ export function AnvilIconWhite({ size = 28, style }: IconProps) {
27
+ return (
28
+ <svg
29
+ width={size}
30
+ height={size}
31
+ viewBox="0 0 32 32"
32
+ fill="none"
33
+ style={style}
34
+ aria-hidden="true"
35
+ >
36
+ <rect x="4" y="6" width="24" height="5" rx="1.5" fill="white" />
37
+ <path d="M2 24h28v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-3z" fill="white" />
38
+ <path d="M8 11h16v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1v-2z" fill="rgba(255,255,255,0.6)" />
39
+ <rect x="10" y="14" width="12" height="4" rx="1" fill="rgba(255,255,255,0.5)" />
40
+ <rect x="6" y="18" width="20" height="3" rx="1" fill="rgba(255,255,255,0.6)" />
41
+ <rect x="4" y="21" width="24" height="3" rx="1" fill="rgba(255,255,255,0.8)" />
42
+ </svg>
43
+ );
44
+ }
45
+
46
+ export function SearchIcon({ size = 16, style }: IconProps) {
47
+ return (
48
+ <svg
49
+ width={size}
50
+ height={size}
51
+ viewBox="0 0 16 16"
52
+ fill="none"
53
+ style={style}
54
+ aria-hidden="true"
55
+ >
56
+ <circle cx="7" cy="7" r="5" stroke="currentColor" strokeWidth="1.5" />
57
+ <path d="M11 11l3.5 3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
58
+ </svg>
59
+ );
60
+ }
61
+
62
+ export function ChevronLeftIcon({ size = 14, style }: IconProps) {
63
+ return (
64
+ <svg
65
+ width={size}
66
+ height={size}
67
+ viewBox="0 0 16 16"
68
+ fill="none"
69
+ style={style}
70
+ aria-hidden="true"
71
+ >
72
+ <path
73
+ d="M10 12L6 8l4-4"
74
+ stroke="currentColor"
75
+ strokeWidth="1.5"
76
+ strokeLinecap="round"
77
+ strokeLinejoin="round"
78
+ />
79
+ </svg>
80
+ );
81
+ }
82
+
83
+ export function GridViewIcon({ size = 14, style }: IconProps) {
84
+ return (
85
+ <svg
86
+ width={size}
87
+ height={size}
88
+ viewBox="0 0 16 16"
89
+ fill="none"
90
+ style={style}
91
+ aria-hidden="true"
92
+ >
93
+ <rect x="2" y="2" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.2" />
94
+ <rect x="9" y="2" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.2" />
95
+ <rect x="2" y="9" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.2" />
96
+ <rect x="9" y="9" width="5" height="5" rx="1" stroke="currentColor" strokeWidth="1.2" />
97
+ </svg>
98
+ );
99
+ }
100
+
101
+ export function DownloadIcon({ size = 14, style }: IconProps) {
102
+ return (
103
+ <svg
104
+ width={size}
105
+ height={size}
106
+ viewBox="0 0 16 16"
107
+ fill="none"
108
+ style={style}
109
+ aria-hidden="true"
110
+ >
111
+ <path
112
+ d="M2 11v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-2"
113
+ stroke="currentColor"
114
+ strokeWidth="1.5"
115
+ strokeLinecap="round"
116
+ />
117
+ <path
118
+ d="M8 2v8M5 7l3 3 3-3"
119
+ stroke="currentColor"
120
+ strokeWidth="1.5"
121
+ strokeLinecap="round"
122
+ strokeLinejoin="round"
123
+ />
124
+ </svg>
125
+ );
126
+ }
127
+
128
+ export function DocumentIcon({ size = 18, style }: IconProps) {
129
+ return (
130
+ <svg
131
+ width={size}
132
+ height={size}
133
+ viewBox="0 0 24 24"
134
+ fill="none"
135
+ style={style}
136
+ aria-hidden="true"
137
+ >
138
+ <path
139
+ d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
140
+ stroke="currentColor"
141
+ strokeWidth="1.5"
142
+ />
143
+ <polyline points="14,2 14,8 20,8" stroke="currentColor" strokeWidth="1.5" />
144
+ <line x1="8" y1="13" x2="16" y2="13" stroke="currentColor" strokeWidth="1.5" />
145
+ <line x1="8" y1="17" x2="16" y2="17" stroke="currentColor" strokeWidth="1.5" />
146
+ </svg>
147
+ );
148
+ }
149
+
150
+ export function BookIcon({ size = 18, style }: IconProps) {
151
+ return (
152
+ <svg
153
+ width={size}
154
+ height={size}
155
+ viewBox="0 0 24 24"
156
+ fill="none"
157
+ style={style}
158
+ aria-hidden="true"
159
+ >
160
+ <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z" stroke="currentColor" strokeWidth="1.5" />
161
+ <path
162
+ d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"
163
+ stroke="currentColor"
164
+ strokeWidth="1.5"
165
+ />
166
+ </svg>
167
+ );
168
+ }
169
+
170
+ export function GridIcon({ size = 18, style }: IconProps) {
171
+ return (
172
+ <svg
173
+ width={size}
174
+ height={size}
175
+ viewBox="0 0 24 24"
176
+ fill="none"
177
+ style={style}
178
+ aria-hidden="true"
179
+ >
180
+ <rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" strokeWidth="1.5" />
181
+ <line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" strokeWidth="1.5" />
182
+ <line x1="9" y1="9" x2="9" y2="21" stroke="currentColor" strokeWidth="1.5" />
183
+ </svg>
184
+ );
185
+ }
186
+
187
+ export function MinusIcon({ size = 14, style }: IconProps) {
188
+ return (
189
+ <svg
190
+ width={size}
191
+ height={size}
192
+ viewBox="0 0 16 16"
193
+ fill="none"
194
+ style={style}
195
+ aria-hidden="true"
196
+ >
197
+ <path d="M3 8h10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
198
+ </svg>
199
+ );
200
+ }
201
+
202
+ export function PlusSmallIcon({ size = 14, style }: IconProps) {
203
+ return (
204
+ <svg
205
+ width={size}
206
+ height={size}
207
+ viewBox="0 0 16 16"
208
+ fill="none"
209
+ style={style}
210
+ aria-hidden="true"
211
+ >
212
+ <path d="M8 3v10M3 8h10" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
213
+ </svg>
214
+ );
215
+ }
216
+
217
+ export function PreviewIcon({ size = 12, style }: IconProps) {
218
+ return (
219
+ <svg
220
+ width={size}
221
+ height={size}
222
+ viewBox="0 0 16 16"
223
+ fill="none"
224
+ style={style}
225
+ aria-hidden="true"
226
+ >
227
+ <rect x="2" y="2" width="12" height="12" rx="1" stroke="currentColor" strokeWidth="1.5" />
228
+ </svg>
229
+ );
230
+ }
231
+
232
+ export function OutlineIcon({ size = 12, style }: IconProps) {
233
+ return (
234
+ <svg
235
+ width={size}
236
+ height={size}
237
+ viewBox="0 0 16 16"
238
+ fill="none"
239
+ style={style}
240
+ aria-hidden="true"
241
+ >
242
+ <path
243
+ d="M2 4h12M2 8h12M2 12h8"
244
+ stroke="currentColor"
245
+ strokeWidth="1.5"
246
+ strokeLinecap="round"
247
+ />
248
+ </svg>
249
+ );
250
+ }
@@ -1,3 +1,9 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { colors, fonts, radii, SIDEBAR_WIDTH } from './design-tokens';
3
+ import { ChevronLeftIcon, DownloadIcon, GridViewIcon } from './icons';
4
+
5
+ type ExportState = 'idle' | 'exporting' | 'error';
6
+
1
7
  interface NavigationProps {
2
8
  pages: Record<string, React.ComponentType>;
3
9
  activePage: string | null;
@@ -5,116 +11,343 @@ interface NavigationProps {
5
11
  documentSlug: string;
6
12
  }
7
13
 
8
- const navStyle: React.CSSProperties = {
9
- position: 'fixed',
10
- top: 0,
11
- left: 0,
12
- width: '240px',
13
- height: '100vh',
14
- background: '#1a1a2e',
15
- color: '#e0e0e0',
16
- padding: '16px',
17
- overflowY: 'auto',
18
- fontFamily: 'system-ui, -apple-system, sans-serif',
19
- fontSize: '14px',
20
- zIndex: 1000,
21
- boxSizing: 'border-box',
22
- };
23
-
24
- const titleStyle: React.CSSProperties = {
25
- fontSize: '16px',
26
- fontWeight: 700,
27
- marginBottom: '16px',
28
- color: '#ffffff',
29
- letterSpacing: '0.5px',
30
- };
31
-
32
- const backLinkStyle: React.CSSProperties = {
33
- display: 'block',
34
- fontSize: '13px',
35
- color: '#888',
36
- textDecoration: 'none',
37
- marginBottom: '12px',
38
- transition: 'color 0.15s',
39
- };
40
-
41
- const buttonBaseStyle: React.CSSProperties = {
42
- display: 'block',
43
- width: '100%',
44
- padding: '8px 12px',
45
- border: 'none',
46
- borderRadius: '6px',
47
- cursor: 'pointer',
48
- textAlign: 'left',
49
- fontSize: '13px',
50
- marginBottom: '4px',
51
- transition: 'background 0.15s',
52
- fontFamily: 'inherit',
53
- };
54
-
55
- function getButtonStyle(isActive: boolean): React.CSSProperties {
56
- return {
57
- ...buttonBaseStyle,
58
- background: isActive ? '#16213e' : 'transparent',
59
- color: isActive ? '#ffffff' : '#b0b0b0',
60
- fontWeight: isActive ? 600 : 400,
61
- };
14
+ function formatSlug(slug: string): string {
15
+ return slug
16
+ .split('-')
17
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
18
+ .join(' ');
62
19
  }
63
20
 
64
- const separatorStyle: React.CSSProperties = {
65
- border: 'none',
66
- borderTop: '1px solid #2a2a4a',
67
- margin: '12px 0',
68
- };
69
-
70
21
  export function Navigation({ pages, activePage, onSelectPage, documentSlug }: NavigationProps) {
71
22
  const pageNames = Object.keys(pages);
23
+ const [exportState, setExportState] = useState<ExportState>('idle');
24
+
25
+ useEffect(() => {
26
+ if (exportState !== 'error') return;
27
+ const timer = setTimeout(() => setExportState('idle'), 3000);
28
+ return () => clearTimeout(timer);
29
+ }, [exportState]);
30
+
31
+ async function handleExport() {
32
+ setExportState('exporting');
33
+ try {
34
+ const res = await fetch(`/api/export/${documentSlug}`, { method: 'POST' });
35
+ if (!res.ok) {
36
+ const body = await res.json().catch(() => ({ error: 'Export failed' }));
37
+ throw new Error(body.error ?? 'Export failed');
38
+ }
39
+ const contentType = res.headers.get('Content-Type') ?? '';
40
+ if (!contentType.includes('application/pdf')) {
41
+ throw new Error('Server returned unexpected response');
42
+ }
43
+ const blob = await res.blob();
44
+ const disposition = res.headers.get('Content-Disposition') ?? '';
45
+ const filenameMatch = disposition.match(/filename="(.+)"/);
46
+ const filename = filenameMatch?.[1] ?? `${documentSlug}.pdf`;
47
+ const url = URL.createObjectURL(blob);
48
+ const a = document.createElement('a');
49
+ a.href = url;
50
+ a.download = filename;
51
+ document.body.appendChild(a);
52
+ a.click();
53
+ a.remove();
54
+ URL.revokeObjectURL(url);
55
+ setExportState('idle');
56
+ } catch {
57
+ setExportState('error');
58
+ }
59
+ }
72
60
 
73
61
  return (
74
- <nav data-pdf-smith-nav="" style={navStyle}>
75
- <a
76
- href="/"
77
- style={backLinkStyle}
78
- onMouseEnter={(e) => {
79
- e.currentTarget.style.color = '#e0e0e0';
80
- }}
81
- onMouseLeave={(e) => {
82
- e.currentTarget.style.color = '#888';
62
+ <nav
63
+ data-pdf-smith-nav=""
64
+ style={{
65
+ position: 'fixed',
66
+ left: 0,
67
+ top: 0,
68
+ bottom: 0,
69
+ width: `${SIDEBAR_WIDTH}px`,
70
+ background: colors.stone900,
71
+ color: colors.stone300,
72
+ display: 'flex',
73
+ flexDirection: 'column',
74
+ zIndex: 100,
75
+ borderRight: `1px solid ${colors.stone800}`,
76
+ fontFamily: fonts.body,
77
+ boxSizing: 'border-box',
78
+ }}
79
+ >
80
+ {/* Header */}
81
+ <div
82
+ style={{
83
+ padding: '16px 20px',
84
+ borderBottom: `1px solid ${colors.stone800}`,
83
85
  }}
84
86
  >
85
- &larr; All Documents
86
- </a>
87
- <div style={titleStyle}>{documentSlug}</div>
88
- <button
89
- type="button"
90
- style={getButtonStyle(activePage === null)}
91
- onClick={() => onSelectPage(null)}
92
- onMouseEnter={(e) => {
93
- if (activePage !== null) e.currentTarget.style.background = '#16213e40';
94
- }}
95
- onMouseLeave={(e) => {
96
- if (activePage !== null) e.currentTarget.style.background = 'transparent';
87
+ <a
88
+ href="/"
89
+ style={{
90
+ display: 'flex',
91
+ alignItems: 'center',
92
+ gap: '6px',
93
+ fontSize: '13px',
94
+ color: colors.stone400,
95
+ textDecoration: 'none',
96
+ transition: 'color 0.15s',
97
+ marginBottom: '12px',
98
+ }}
99
+ onMouseEnter={(e) => {
100
+ e.currentTarget.style.color = colors.stone200;
101
+ }}
102
+ onMouseLeave={(e) => {
103
+ e.currentTarget.style.color = colors.stone400;
104
+ }}
105
+ >
106
+ <ChevronLeftIcon size={14} />
107
+ All Documents
108
+ </a>
109
+ <div
110
+ style={{
111
+ fontFamily: fonts.heading,
112
+ fontSize: '16px',
113
+ fontWeight: 600,
114
+ color: colors.stone100,
115
+ }}
116
+ >
117
+ {formatSlug(documentSlug)}
118
+ </div>
119
+ <div
120
+ style={{
121
+ fontFamily: fonts.mono,
122
+ fontSize: '11px',
123
+ color: colors.stone500,
124
+ marginTop: '2px',
125
+ }}
126
+ >
127
+ pdfs/{documentSlug}
128
+ </div>
129
+ </div>
130
+
131
+ {/* Page Navigation */}
132
+ <div style={{ flex: 1, overflowY: 'auto', padding: '12px' }}>
133
+ <div
134
+ style={{
135
+ fontSize: '11px',
136
+ fontWeight: 600,
137
+ textTransform: 'uppercase',
138
+ letterSpacing: '0.06em',
139
+ color: colors.stone500,
140
+ padding: '8px 8px 6px',
141
+ }}
142
+ >
143
+ Pages
144
+ </div>
145
+
146
+ {/* All Pages button */}
147
+ <button
148
+ type="button"
149
+ onClick={() => onSelectPage(null)}
150
+ style={{
151
+ display: 'flex',
152
+ alignItems: 'center',
153
+ gap: '10px',
154
+ padding: '8px 10px',
155
+ borderRadius: radii.md,
156
+ color: activePage === null ? colors.ember : colors.stone300,
157
+ fontSize: '13px',
158
+ fontWeight: 500,
159
+ fontFamily: 'inherit',
160
+ transition: 'all 0.12s',
161
+ cursor: 'pointer',
162
+ marginBottom: '8px',
163
+ border: `1px solid ${activePage === null ? colors.forge : colors.stone700}`,
164
+ background: activePage === null ? 'rgba(194, 65, 12, 0.1)' : 'transparent',
165
+ width: '100%',
166
+ textAlign: 'left',
167
+ }}
168
+ onMouseEnter={(e) => {
169
+ if (activePage !== null) {
170
+ e.currentTarget.style.background = 'rgba(250, 249, 247, 0.05)';
171
+ e.currentTarget.style.borderColor = colors.stone600;
172
+ }
173
+ }}
174
+ onMouseLeave={(e) => {
175
+ if (activePage !== null) {
176
+ e.currentTarget.style.background = 'transparent';
177
+ e.currentTarget.style.borderColor = colors.stone700;
178
+ }
179
+ }}
180
+ >
181
+ <GridViewIcon size={14} />
182
+ All Pages
183
+ </button>
184
+
185
+ {/* Individual page links */}
186
+ {pageNames.map((name, idx) => {
187
+ const isActive = activePage === name;
188
+ return (
189
+ <button
190
+ type="button"
191
+ key={name}
192
+ onClick={() => onSelectPage(name)}
193
+ style={{
194
+ display: 'flex',
195
+ alignItems: 'center',
196
+ gap: '10px',
197
+ padding: '8px 10px',
198
+ borderRadius: radii.md,
199
+ color: isActive ? colors.ember : colors.stone400,
200
+ fontSize: '13px',
201
+ fontFamily: 'inherit',
202
+ transition: 'all 0.12s',
203
+ cursor: 'pointer',
204
+ marginBottom: '2px',
205
+ border: 'none',
206
+ background: isActive ? 'rgba(194, 65, 12, 0.12)' : 'transparent',
207
+ width: '100%',
208
+ textAlign: 'left',
209
+ }}
210
+ onMouseEnter={(e) => {
211
+ if (!isActive) {
212
+ e.currentTarget.style.background = 'rgba(250, 249, 247, 0.05)';
213
+ e.currentTarget.style.color = colors.stone200;
214
+ }
215
+ }}
216
+ onMouseLeave={(e) => {
217
+ if (!isActive) {
218
+ e.currentTarget.style.background = 'transparent';
219
+ e.currentTarget.style.color = colors.stone400;
220
+ }
221
+ }}
222
+ >
223
+ <span
224
+ style={{
225
+ width: '22px',
226
+ height: '22px',
227
+ display: 'flex',
228
+ alignItems: 'center',
229
+ justifyContent: 'center',
230
+ fontFamily: fonts.mono,
231
+ fontSize: '11px',
232
+ fontWeight: 600,
233
+ background: isActive ? colors.forge : colors.stone800,
234
+ color: isActive ? 'white' : 'inherit',
235
+ borderRadius: '5px',
236
+ flexShrink: 0,
237
+ }}
238
+ >
239
+ {idx + 1}
240
+ </span>
241
+ <span
242
+ style={{
243
+ overflow: 'hidden',
244
+ textOverflow: 'ellipsis',
245
+ whiteSpace: 'nowrap',
246
+ }}
247
+ >
248
+ {name}
249
+ </span>
250
+ </button>
251
+ );
252
+ })}
253
+ </div>
254
+
255
+ {/* Footer */}
256
+ <div
257
+ style={{
258
+ padding: '12px 16px',
259
+ borderTop: `1px solid ${colors.stone800}`,
260
+ display: 'flex',
261
+ flexDirection: 'column',
262
+ gap: '8px',
97
263
  }}
98
264
  >
99
- All Pages
100
- </button>
101
- <hr style={separatorStyle} />
102
- {pageNames.map((name) => (
103
265
  <button
104
266
  type="button"
105
- key={name}
106
- style={getButtonStyle(activePage === name)}
107
- onClick={() => onSelectPage(name)}
267
+ disabled={exportState === 'exporting'}
268
+ onClick={handleExport}
269
+ style={{
270
+ display: 'flex',
271
+ alignItems: 'center',
272
+ justifyContent: 'center',
273
+ gap: '6px',
274
+ padding: '8px 16px',
275
+ background:
276
+ exportState === 'error'
277
+ ? '#DC2626'
278
+ : exportState === 'exporting'
279
+ ? colors.stone600
280
+ : colors.forge,
281
+ color: 'white',
282
+ border: 'none',
283
+ borderRadius: radii.md,
284
+ fontFamily: fonts.body,
285
+ fontSize: '13px',
286
+ fontWeight: 500,
287
+ cursor: exportState === 'exporting' ? 'not-allowed' : 'pointer',
288
+ transition: 'background 0.15s',
289
+ width: '100%',
290
+ opacity: exportState === 'exporting' ? 0.8 : 1,
291
+ }}
108
292
  onMouseEnter={(e) => {
109
- if (activePage !== name) e.currentTarget.style.background = '#16213e40';
293
+ if (exportState === 'idle') e.currentTarget.style.background = colors.deepForge;
110
294
  }}
111
295
  onMouseLeave={(e) => {
112
- if (activePage !== name) e.currentTarget.style.background = 'transparent';
296
+ if (exportState === 'idle') e.currentTarget.style.background = colors.forge;
113
297
  }}
114
298
  >
115
- {name}
299
+ {exportState === 'exporting' ? (
300
+ <>
301
+ <style>{`@keyframes pdf-smith-spin { to { transform: rotate(360deg) } }`}</style>
302
+ <span
303
+ style={{
304
+ width: '14px',
305
+ height: '14px',
306
+ border: '2px solid rgba(255,255,255,0.3)',
307
+ borderTopColor: 'white',
308
+ borderRadius: '50%',
309
+ animation: 'pdf-smith-spin 0.6s linear infinite',
310
+ flexShrink: 0,
311
+ }}
312
+ />
313
+ Exporting...
314
+ </>
315
+ ) : exportState === 'error' ? (
316
+ 'Export Failed'
317
+ ) : (
318
+ <>
319
+ <DownloadIcon size={14} />
320
+ Export PDF
321
+ </>
322
+ )}
116
323
  </button>
117
- ))}
324
+ <div
325
+ style={{
326
+ display: 'flex',
327
+ alignItems: 'center',
328
+ justifyContent: 'space-between',
329
+ fontSize: '11px',
330
+ color: colors.stone500,
331
+ fontFamily: fonts.mono,
332
+ }}
333
+ >
334
+ <span>
335
+ {pageNames.length} page{pageNames.length !== 1 ? 's' : ''}
336
+ </span>
337
+ <span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
338
+ <span
339
+ style={{
340
+ width: '6px',
341
+ height: '6px',
342
+ borderRadius: '50%',
343
+ background: colors.success,
344
+ display: 'inline-block',
345
+ }}
346
+ />
347
+ HMR active
348
+ </span>
349
+ </div>
350
+ </div>
118
351
  </nav>
119
352
  );
120
353
  }