saifisvibin commited on
Commit
9218ec0
·
verified ·
1 Parent(s): 1b4baf9

Upload 5 files

Browse files
Files changed (6) hide show
  1. .gitattributes +1 -0
  2. Machine-Learning-Systems.pdf +3 -0
  3. README.md +162 -0
  4. index.html +219 -0
  5. script.js +457 -0
  6. styles.css +812 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ Machine-Learning-Systems.pdf filter=lfs diff=lfs merge=lfs -text
Machine-Learning-Systems.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:882ae6b12bda753a49919cf6e57e9d5b8efb42f708a2bd0268aac8621eb62cb6
3
+ size 42590610
README.md ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Machine Learning Systems
3
+ emoji: 📚
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: static
7
+ pinned: false
8
+ ---
9
+
10
+ # Machine Learning Systems PDF Viewer
11
+
12
+ A stunning, modern web application for viewing the Machine Learning Systems PDF. Built with vanilla JavaScript, HTML, and CSS, featuring a premium dark mode design with glassmorphism effects.
13
+
14
+ ## ✨ Features
15
+
16
+ - 📄 **PDF.js Integration** - Smooth in-browser PDF viewing
17
+ - 🎨 **Premium Design** - Dark mode with vibrant gradients and glassmorphism
18
+ - 🔍 **Search Functionality** - Find text across all pages
19
+ - ⌨️ **Keyboard Shortcuts** - Navigate efficiently with hotkeys
20
+ - 📱 **Fully Responsive** - Works beautifully on all devices
21
+ - 🎯 **Page Thumbnails** - Quick navigation with visual previews
22
+ - 🔎 **Zoom Controls** - Multiple zoom options including fit-to-width
23
+ - 🖼️ **Fullscreen Mode** - Distraction-free reading experience
24
+
25
+ ## 🚀 Deployment to Hugging Face Spaces
26
+
27
+ ### Step 1: Create a New Space
28
+
29
+ 1. Go to [Hugging Face](https://huggingface.co/) and sign in
30
+ 2. Click on your profile → **New Space**
31
+ 3. Choose:
32
+ - **Space name**: `ml-systems-pdf-viewer` (or your preferred name)
33
+ - **License**: Choose your preferred license
34
+ - **Space SDK**: Select **Static**
35
+ - **Visibility**: Public or Private
36
+
37
+ ### Step 2: Upload Files
38
+
39
+ You can upload files either through the web interface or using Git:
40
+
41
+ #### Option A: Web Interface
42
+ 1. In your Space, click **Files** → **Add file** → **Upload files**
43
+ 2. Upload these files:
44
+ - `index.html`
45
+ - `styles.css`
46
+ - `script.js`
47
+ - `Machine-Learning-Systems.pdf`
48
+ - `README.md` (this file)
49
+
50
+ #### Option B: Git (Recommended)
51
+ ```bash
52
+ # Clone your Space repository
53
+ git clone https://huggingface.co/spaces/YOUR_USERNAME/YOUR_SPACE_NAME
54
+ cd YOUR_SPACE_NAME
55
+
56
+ # Copy all files
57
+ cp index.html styles.css script.js Machine-Learning-Systems.pdf README.md ./
58
+
59
+ # Commit and push
60
+ git add .
61
+ git commit -m "Initial commit: PDF viewer application"
62
+ git push
63
+ ```
64
+
65
+ ### Step 3: Configure Custom Domain
66
+
67
+ 1. Go to your Space settings
68
+ 2. Navigate to **Settings** → **Domains**
69
+ 3. Click **Add a domain**
70
+ 4. Follow the instructions to:
71
+ - Add your custom domain (e.g., `ml-systems.yourdomain.com`)
72
+ - Update your DNS records with the provided CNAME
73
+ - Wait for DNS propagation (can take up to 48 hours)
74
+
75
+ **DNS Configuration Example:**
76
+ ```
77
+ Type: CNAME
78
+ Name: ml-systems (or your subdomain)
79
+ Value: spaces.huggingface.co
80
+ TTL: 3600
81
+ ```
82
+
83
+ 5. Once DNS is propagated, enable SSL certificate (automatic via Let's Encrypt)
84
+
85
+ ## 💻 Local Development
86
+
87
+ To run this project locally:
88
+
89
+ 1. **Clone or download** this repository
90
+ 2. **Ensure all files are in the same directory**:
91
+ - `index.html`
92
+ - `styles.css`
93
+ - `script.js`
94
+ - `Machine-Learning-Systems.pdf`
95
+ 3. **Open `index.html`** in a modern web browser (Chrome, Firefox, Safari, or Edge)
96
+
97
+ > **Note**: For the best experience, serve the files through a local web server rather than opening the HTML file directly. You can use:
98
+ > - Python: `python -m http.server 8000`
99
+ > - Node.js: `npx http-server`
100
+ > - VS Code: Use the Live Server extension
101
+
102
+ ## ⌨️ Keyboard Shortcuts
103
+
104
+ - `←` / `→` - Navigate between pages
105
+ - `↑` / `↓` - Navigate between pages (alternative)
106
+ - `+` / `-` - Zoom in/out
107
+ - `Ctrl+F` - Open search
108
+ - `F` - Toggle fullscreen
109
+ - `Ctrl+Wheel` - Zoom with mouse wheel
110
+
111
+ ## 🎨 Design Features
112
+
113
+ - **Glassmorphism UI** - Frosted glass effect with backdrop blur
114
+ - **Vibrant Gradients** - Purple, blue, and cyan color scheme
115
+ - **Smooth Animations** - Micro-interactions for better UX
116
+ - **Responsive Layout** - Adapts to mobile, tablet, and desktop
117
+ - **Dark Mode** - Eye-friendly dark theme optimized for reading
118
+
119
+ ## 🔧 Customization
120
+
121
+ ### Change Colors
122
+ Edit the CSS custom properties in `styles.css`:
123
+ ```css
124
+ :root {
125
+ --color-accent-primary: #a855f7; /* Purple */
126
+ --color-accent-secondary: #3b82f6; /* Blue */
127
+ --color-accent-tertiary: #06b6d4; /* Cyan */
128
+ }
129
+ ```
130
+
131
+ ### Default Zoom Level
132
+ Edit `script.js`:
133
+ ```javascript
134
+ const state = {
135
+ scale: 1.5, // Change this value (1.0 = 100%)
136
+ // ...
137
+ };
138
+ ```
139
+
140
+ ## 📦 Dependencies
141
+
142
+ - [PDF.js](https://mozilla.github.io/pdf.js/) v3.11.174 (loaded via CDN)
143
+ - [Google Fonts](https://fonts.google.com/) - Inter & Fira Code
144
+
145
+ ## 🌐 Browser Support
146
+
147
+ - Chrome/Edge (latest)
148
+ - Firefox (latest)
149
+ - Safari (latest)
150
+ - Mobile browsers (iOS Safari, Chrome Mobile)
151
+
152
+ ## 📝 License
153
+
154
+ This PDF viewer application is provided as-is. The PDF content may have its own license.
155
+
156
+ ## 🤝 Contributing
157
+
158
+ Feel free to fork this project and customize it for your needs!
159
+
160
+ ---
161
+
162
+ Built with ❤️ for the Machine Learning community
index.html ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description"
8
+ content="Machine Learning Systems - An interactive PDF viewer for exploring machine learning systems concepts, architectures, and best practices.">
9
+ <meta name="keywords" content="machine learning, ML systems, AI, deep learning, MLOps">
10
+ <meta name="author" content="Machine Learning Systems">
11
+
12
+ <title>Machine Learning Systems | Interactive PDF Viewer</title>
13
+
14
+ <!-- Fonts -->
15
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
+ <link
18
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Fira+Code:wght@400;500&display=swap"
19
+ rel="stylesheet">
20
+
21
+ <!-- Styles -->
22
+ <link rel="stylesheet" href="styles.css">
23
+
24
+ <!-- PDF.js -->
25
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
26
+ </head>
27
+
28
+ <body>
29
+ <!-- Header -->
30
+ <header class="header">
31
+ <div class="header-content">
32
+ <div class="logo-section">
33
+ <div class="logo-icon">
34
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
35
+ <path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="currentColor" stroke-width="2"
36
+ stroke-linecap="round" stroke-linejoin="round" />
37
+ <path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round"
38
+ stroke-linejoin="round" />
39
+ <path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"
40
+ stroke-linejoin="round" />
41
+ </svg>
42
+ </div>
43
+ <div class="logo-text">
44
+ <h1>Machine Learning Systems</h1>
45
+ <p class="subtitle">Interactive PDF Viewer</p>
46
+ </div>
47
+ </div>
48
+
49
+ <div class="header-actions">
50
+ <button class="btn-icon" id="themeToggle" title="Toggle theme">
51
+ <svg id="sunIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
52
+ <circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2" />
53
+ <path
54
+ d="M12 1V3M12 21V23M4.22 4.22L5.64 5.64M18.36 18.36L19.78 19.78M1 12H3M21 12H23M4.22 19.78L5.64 18.36M18.36 5.64L19.78 4.22"
55
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" />
56
+ </svg>
57
+ <svg id="moonIcon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
58
+ style="display: none;">
59
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" stroke-width="2"
60
+ stroke-linecap="round" stroke-linejoin="round" />
61
+ </svg>
62
+ </button>
63
+ <button class="btn-icon" id="searchBtn" title="Search (Ctrl+F)">
64
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
65
+ <circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" />
66
+ <path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
67
+ </svg>
68
+ </button>
69
+ <button class="btn-icon" id="fullscreenBtn" title="Fullscreen">
70
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
71
+ <path
72
+ d="M8 3H5C3.89543 3 3 3.89543 3 5V8M21 8V5C21 3.89543 20.1046 3 19 3H16M16 21H19C20.1046 21 21 20.1046 21 19V16M3 16V19C3 20.1046 3.89543 21 5 21H8"
73
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" />
74
+ </svg>
75
+ </button>
76
+ <a href="Machine-Learning-Systems.pdf" download class="btn-primary">
77
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
78
+ <path d="M21 15V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V15"
79
+ stroke="currentColor" stroke-width="2" stroke-linecap="round" />
80
+ <path d="M7 10L12 15L17 10" stroke="currentColor" stroke-width="2" stroke-linecap="round"
81
+ stroke-linejoin="round" />
82
+ <path d="M12 15V3" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
83
+ </svg>
84
+ Download PDF
85
+ </a>
86
+ </div>
87
+ </div>
88
+ </header>
89
+
90
+ <!-- Main Content -->
91
+ <main class="main-container">
92
+ <!-- Sidebar -->
93
+ <aside class="sidebar" id="sidebar">
94
+ <div class="sidebar-header">
95
+ <h2>Navigation</h2>
96
+ <button class="btn-icon" id="closeSidebarBtn" title="Close sidebar">
97
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
98
+ <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
99
+ </svg>
100
+ </button>
101
+ </div>
102
+
103
+ <div class="page-info">
104
+ <div class="info-item">
105
+ <span class="info-label">Current Page</span>
106
+ <span class="info-value" id="currentPageDisplay">1</span>
107
+ </div>
108
+ <div class="info-item">
109
+ <span class="info-label">Total Pages</span>
110
+ <span class="info-value" id="totalPagesDisplay">-</span>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="page-navigation">
115
+ <label for="pageInput">Go to Page</label>
116
+ <div class="page-input-group">
117
+ <input type="number" id="pageInput" min="1" placeholder="1">
118
+ <button class="btn-secondary" id="goToPageBtn">Go</button>
119
+ </div>
120
+ </div>
121
+
122
+ <div class="thumbnails" id="thumbnails">
123
+ <p class="loading-text">Loading pages...</p>
124
+ </div>
125
+ </aside>
126
+
127
+ <!-- PDF Viewer -->
128
+ <div class="viewer-container">
129
+ <button class="sidebar-toggle" id="sidebarToggle" title="Toggle sidebar">
130
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
131
+ <path d="M3 12H21M3 6H21M3 18H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
132
+ </svg>
133
+ </button>
134
+
135
+ <!-- Search Panel -->
136
+ <div class="search-panel" id="searchPanel">
137
+ <div class="search-input-group">
138
+ <input type="text" id="searchInput" placeholder="Search in PDF...">
139
+ <button class="btn-secondary" id="searchNextBtn">Next</button>
140
+ <button class="btn-secondary" id="searchPrevBtn">Previous</button>
141
+ <button class="btn-icon" id="closeSearchBtn">
142
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
143
+ <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2"
144
+ stroke-linecap="round" />
145
+ </svg>
146
+ </button>
147
+ </div>
148
+ <p class="search-results" id="searchResults"></p>
149
+ </div>
150
+
151
+ <!-- Controls -->
152
+ <div class="controls">
153
+ <div class="control-group">
154
+ <button class="btn-icon" id="prevPageBtn" title="Previous page (←)">
155
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
156
+ <path d="M15 18L9 12L15 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"
157
+ stroke-linejoin="round" />
158
+ </svg>
159
+ </button>
160
+
161
+ <span class="page-indicator">
162
+ <span id="pageNum">1</span> / <span id="pageCount">-</span>
163
+ </span>
164
+
165
+ <button class="btn-icon" id="nextPageBtn" title="Next page (→)">
166
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
167
+ <path d="M9 18L15 12L9 6" stroke="currentColor" stroke-width="2" stroke-linecap="round"
168
+ stroke-linejoin="round" />
169
+ </svg>
170
+ </button>
171
+ </div>
172
+
173
+ <div class="control-group">
174
+ <button class="btn-icon" id="zoomOutBtn" title="Zoom out (-)">
175
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
176
+ <circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" />
177
+ <path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2"
178
+ stroke-linecap="round" />
179
+ <path d="M8 11H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
180
+ </svg>
181
+ </button>
182
+
183
+ <span class="zoom-level" id="zoomLevel">100%</span>
184
+
185
+ <button class="btn-icon" id="zoomInBtn" title="Zoom in (+)">
186
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
187
+ <circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" />
188
+ <path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2"
189
+ stroke-linecap="round" />
190
+ <path d="M11 8V14M8 11H14" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
191
+ </svg>
192
+ </button>
193
+
194
+ <button class="btn-icon" id="fitWidthBtn" title="Fit to width">
195
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
196
+ <rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" />
197
+ <path d="M9 12H15M9 12L11 10M9 12L11 14M15 12L13 10M15 12L13 14" stroke="currentColor"
198
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
199
+ </svg>
200
+ </button>
201
+ </div>
202
+ </div>
203
+
204
+ <!-- Canvas Container -->
205
+ <div class="canvas-container" id="canvasContainer">
206
+ <div class="loading-overlay" id="loadingOverlay">
207
+ <div class="spinner"></div>
208
+ <p>Loading PDF...</p>
209
+ </div>
210
+ <canvas id="pdfCanvas"></canvas>
211
+ </div>
212
+ </div>
213
+ </main>
214
+
215
+ <!-- Scripts -->
216
+ <script src="script.js"></script>
217
+ </body>
218
+
219
+ </html>
script.js ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // PDF.js Configuration
2
+ pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
3
+
4
+ // Theme Management
5
+ const themeToggle = document.getElementById('themeToggle');
6
+ const sunIcon = document.getElementById('sunIcon');
7
+ const moonIcon = document.getElementById('moonIcon');
8
+
9
+ // Initialize theme from localStorage or default to dark
10
+ const currentTheme = localStorage.getItem('theme') || 'dark';
11
+ if (currentTheme === 'light') {
12
+ document.documentElement.setAttribute('data-theme', 'light');
13
+ sunIcon.style.display = 'none';
14
+ moonIcon.style.display = 'block';
15
+ }
16
+
17
+ // Toggle theme function
18
+ function toggleTheme() {
19
+ const currentTheme = document.documentElement.getAttribute('data-theme');
20
+ const newTheme = currentTheme === 'light' ? 'dark' : 'light';
21
+
22
+ document.documentElement.setAttribute('data-theme', newTheme);
23
+ localStorage.setItem('theme', newTheme);
24
+
25
+ // Toggle icons
26
+ if (newTheme === 'light') {
27
+ sunIcon.style.display = 'none';
28
+ moonIcon.style.display = 'block';
29
+ } else {
30
+ sunIcon.style.display = 'block';
31
+ moonIcon.style.display = 'none';
32
+ }
33
+ }
34
+
35
+ // State Management
36
+ const state = {
37
+ pdfDoc: null,
38
+ pageNum: 1,
39
+ pageRendering: false,
40
+ pageNumPending: null,
41
+ scale: 1.5,
42
+ canvas: document.getElementById('pdfCanvas'),
43
+ ctx: null
44
+ };
45
+
46
+ // Initialize
47
+ state.ctx = state.canvas.getContext('2d');
48
+
49
+ // DOM Elements
50
+ const elements = {
51
+ loadingOverlay: document.getElementById('loadingOverlay'),
52
+ pageNum: document.getElementById('pageNum'),
53
+ pageCount: document.getElementById('pageCount'),
54
+ currentPageDisplay: document.getElementById('currentPageDisplay'),
55
+ totalPagesDisplay: document.getElementById('totalPagesDisplay'),
56
+ zoomLevel: document.getElementById('zoomLevel'),
57
+ prevPageBtn: document.getElementById('prevPageBtn'),
58
+ nextPageBtn: document.getElementById('nextPageBtn'),
59
+ zoomInBtn: document.getElementById('zoomInBtn'),
60
+ zoomOutBtn: document.getElementById('zoomOutBtn'),
61
+ fitWidthBtn: document.getElementById('fitWidthBtn'),
62
+ pageInput: document.getElementById('pageInput'),
63
+ goToPageBtn: document.getElementById('goToPageBtn'),
64
+ sidebar: document.getElementById('sidebar'),
65
+ sidebarToggle: document.getElementById('sidebarToggle'),
66
+ closeSidebarBtn: document.getElementById('closeSidebarBtn'),
67
+ searchBtn: document.getElementById('searchBtn'),
68
+ searchPanel: document.getElementById('searchPanel'),
69
+ closeSearchBtn: document.getElementById('closeSearchBtn'),
70
+ fullscreenBtn: document.getElementById('fullscreenBtn'),
71
+ canvasContainer: document.getElementById('canvasContainer')
72
+ };
73
+
74
+ // Render Page
75
+ function renderPage(num) {
76
+ state.pageRendering = true;
77
+
78
+ state.pdfDoc.getPage(num).then(page => {
79
+ const viewport = page.getViewport({ scale: state.scale });
80
+ state.canvas.height = viewport.height;
81
+ state.canvas.width = viewport.width;
82
+
83
+ const renderContext = {
84
+ canvasContext: state.ctx,
85
+ viewport: viewport
86
+ };
87
+
88
+ const renderTask = page.render(renderContext);
89
+
90
+ renderTask.promise.then(() => {
91
+ state.pageRendering = false;
92
+
93
+ if (state.pageNumPending !== null) {
94
+ renderPage(state.pageNumPending);
95
+ state.pageNumPending = null;
96
+ }
97
+
98
+ // Update UI
99
+ updatePageDisplay();
100
+
101
+ // Hide loading overlay on first render
102
+ if (num === 1) {
103
+ elements.loadingOverlay.classList.add('hidden');
104
+ }
105
+ });
106
+ });
107
+
108
+ // Update page display immediately
109
+ elements.pageNum.textContent = num;
110
+ elements.currentPageDisplay.textContent = num;
111
+ }
112
+
113
+ // Queue Page Render
114
+ function queueRenderPage(num) {
115
+ if (state.pageRendering) {
116
+ state.pageNumPending = num;
117
+ } else {
118
+ renderPage(num);
119
+ }
120
+ }
121
+
122
+ // Update Page Display
123
+ function updatePageDisplay() {
124
+ elements.zoomLevel.textContent = Math.round(state.scale * 100) + '%';
125
+ updateThumbnailSelection();
126
+ }
127
+
128
+ // Navigate to Previous Page
129
+ function onPrevPage() {
130
+ if (state.pageNum <= 1) {
131
+ return;
132
+ }
133
+ state.pageNum--;
134
+ queueRenderPage(state.pageNum);
135
+ }
136
+
137
+ // Navigate to Next Page
138
+ function onNextPage() {
139
+ if (state.pageNum >= state.pdfDoc.numPages) {
140
+ return;
141
+ }
142
+ state.pageNum++;
143
+ queueRenderPage(state.pageNum);
144
+ }
145
+
146
+ // Zoom In
147
+ function zoomIn() {
148
+ if (state.scale < 3) {
149
+ state.scale += 0.25;
150
+ queueRenderPage(state.pageNum);
151
+ }
152
+ }
153
+
154
+ // Zoom Out
155
+ function zoomOut() {
156
+ if (state.scale > 0.5) {
157
+ state.scale -= 0.25;
158
+ queueRenderPage(state.pageNum);
159
+ }
160
+ }
161
+
162
+ // Fit to Width
163
+ function fitToWidth() {
164
+ const containerWidth = elements.canvasContainer.clientWidth - 40;
165
+ state.pdfDoc.getPage(state.pageNum).then(page => {
166
+ const viewport = page.getViewport({ scale: 1 });
167
+ state.scale = containerWidth / viewport.width;
168
+ queueRenderPage(state.pageNum);
169
+ });
170
+ }
171
+
172
+ // Go to Page
173
+ function goToPage() {
174
+ const pageNumber = parseInt(elements.pageInput.value);
175
+ if (pageNumber >= 1 && pageNumber <= state.pdfDoc.numPages) {
176
+ state.pageNum = pageNumber;
177
+ queueRenderPage(state.pageNum);
178
+ elements.pageInput.value = '';
179
+ }
180
+ }
181
+
182
+ // Toggle Sidebar
183
+ function toggleSidebar() {
184
+ elements.sidebar.classList.toggle('hidden');
185
+ }
186
+
187
+ // Toggle Search
188
+ function toggleSearch() {
189
+ elements.searchPanel.classList.toggle('active');
190
+ if (elements.searchPanel.classList.contains('active')) {
191
+ document.getElementById('searchInput').focus();
192
+ }
193
+ }
194
+
195
+ // Toggle Fullscreen
196
+ function toggleFullscreen() {
197
+ if (!document.fullscreenElement) {
198
+ document.documentElement.requestFullscreen();
199
+ } else {
200
+ if (document.exitFullscreen) {
201
+ document.exitFullscreen();
202
+ }
203
+ }
204
+ }
205
+
206
+ // Event Listeners
207
+ themeToggle.addEventListener('click', toggleTheme);
208
+ elements.prevPageBtn.addEventListener('click', onPrevPage);
209
+ elements.nextPageBtn.addEventListener('click', onNextPage);
210
+ elements.zoomInBtn.addEventListener('click', zoomIn);
211
+ elements.zoomOutBtn.addEventListener('click', zoomOut);
212
+ elements.fitWidthBtn.addEventListener('click', fitToWidth);
213
+ elements.goToPageBtn.addEventListener('click', goToPage);
214
+ elements.sidebarToggle.addEventListener('click', toggleSidebar);
215
+ elements.closeSidebarBtn.addEventListener('click', toggleSidebar);
216
+ elements.searchBtn.addEventListener('click', toggleSearch);
217
+ elements.closeSearchBtn.addEventListener('click', toggleSearch);
218
+ elements.fullscreenBtn.addEventListener('click', toggleFullscreen);
219
+
220
+ // Enter key for page input
221
+ elements.pageInput.addEventListener('keypress', (e) => {
222
+ if (e.key === 'Enter') {
223
+ goToPage();
224
+ }
225
+ });
226
+
227
+ // Keyboard Shortcuts
228
+ document.addEventListener('keydown', (e) => {
229
+ // Arrow keys for navigation
230
+ if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
231
+ e.preventDefault();
232
+ onPrevPage();
233
+ } else if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
234
+ e.preventDefault();
235
+ onNextPage();
236
+ }
237
+
238
+ // Zoom shortcuts
239
+ if (e.key === '+' || e.key === '=') {
240
+ e.preventDefault();
241
+ zoomIn();
242
+ } else if (e.key === '-') {
243
+ e.preventDefault();
244
+ zoomOut();
245
+ }
246
+
247
+ // Search shortcut
248
+ if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
249
+ e.preventDefault();
250
+ toggleSearch();
251
+ }
252
+
253
+ // Fullscreen shortcut
254
+ if (e.key === 'f' && !e.ctrlKey && !e.metaKey) {
255
+ const activeElement = document.activeElement;
256
+ if (activeElement.tagName !== 'INPUT') {
257
+ toggleFullscreen();
258
+ }
259
+ }
260
+ });
261
+
262
+ // Search Functionality
263
+ let searchMatches = [];
264
+ let currentMatch = 0;
265
+
266
+ document.getElementById('searchInput').addEventListener('input', async (e) => {
267
+ const query = e.target.value.toLowerCase();
268
+ const results = document.getElementById('searchResults');
269
+
270
+ if (query.length < 2) {
271
+ results.textContent = '';
272
+ searchMatches = [];
273
+ return;
274
+ }
275
+
276
+ searchMatches = [];
277
+
278
+ // Search through all pages
279
+ for (let i = 1; i <= state.pdfDoc.numPages; i++) {
280
+ const page = await state.pdfDoc.getPage(i);
281
+ const textContent = await page.getTextContent();
282
+ const text = textContent.items.map(item => item.str).join(' ').toLowerCase();
283
+
284
+ if (text.includes(query)) {
285
+ searchMatches.push(i);
286
+ }
287
+ }
288
+
289
+ if (searchMatches.length > 0) {
290
+ results.textContent = `Found ${searchMatches.length} matches`;
291
+ currentMatch = 0;
292
+ } else {
293
+ results.textContent = 'No matches found';
294
+ }
295
+ });
296
+
297
+ document.getElementById('searchNextBtn').addEventListener('click', () => {
298
+ if (searchMatches.length > 0) {
299
+ currentMatch = (currentMatch + 1) % searchMatches.length;
300
+ state.pageNum = searchMatches[currentMatch];
301
+ queueRenderPage(state.pageNum);
302
+ document.getElementById('searchResults').textContent =
303
+ `Match ${currentMatch + 1} of ${searchMatches.length} (Page ${searchMatches[currentMatch]})`;
304
+ }
305
+ });
306
+
307
+ document.getElementById('searchPrevBtn').addEventListener('click', () => {
308
+ if (searchMatches.length > 0) {
309
+ currentMatch = (currentMatch - 1 + searchMatches.length) % searchMatches.length;
310
+ state.pageNum = searchMatches[currentMatch];
311
+ queueRenderPage(state.pageNum);
312
+ document.getElementById('searchResults').textContent =
313
+ `Match ${currentMatch + 1} of ${searchMatches.length} (Page ${searchMatches[currentMatch]})`;
314
+ }
315
+ });
316
+
317
+ // Mouse Wheel Zoom
318
+ elements.canvasContainer.addEventListener('wheel', (e) => {
319
+ if (e.ctrlKey) {
320
+ e.preventDefault();
321
+ if (e.deltaY < 0) {
322
+ zoomIn();
323
+ } else {
324
+ zoomOut();
325
+ }
326
+ }
327
+ });
328
+
329
+ // Load PDF
330
+ const url = 'Machine-Learning-Systems.pdf';
331
+
332
+ pdfjsLib.getDocument(url).promise.then(pdfDoc => {
333
+ state.pdfDoc = pdfDoc;
334
+ elements.pageCount.textContent = pdfDoc.numPages;
335
+ elements.totalPagesDisplay.textContent = pdfDoc.numPages;
336
+ elements.pageInput.max = pdfDoc.numPages;
337
+
338
+ // Render the first page
339
+ renderPage(state.pageNum);
340
+
341
+ // Generate thumbnails
342
+ generateThumbnails();
343
+ }).catch(error => {
344
+ console.error('Error loading PDF:', error);
345
+ elements.loadingOverlay.innerHTML = `
346
+ <div style="text-align: center;">
347
+ <h2 style="color: var(--color-accent-primary); margin-bottom: 1rem;">Error Loading PDF</h2>
348
+ <p style="color: var(--color-text-secondary);">Please make sure the PDF file is in the same directory as this HTML file.</p>
349
+ <p style="color: var(--color-text-muted); margin-top: 1rem; font-size: 0.875rem;">Looking for: ${url}</p>
350
+ </div>
351
+ `;
352
+ });
353
+
354
+ // Generate Thumbnails
355
+ let currentThumbnailCount = 20;
356
+ const thumbnailsPerLoad = 20;
357
+
358
+ async function generateThumbnails(startFrom = 1, count = 20) {
359
+ const thumbnailsContainer = document.getElementById('thumbnails');
360
+
361
+ if (startFrom === 1) {
362
+ thumbnailsContainer.innerHTML = '<h3 style="font-size: 0.875rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--color-text-secondary);">Pages</h3>';
363
+ }
364
+
365
+ const endAt = Math.min(startFrom + count - 1, state.pdfDoc.numPages);
366
+
367
+ for (let i = startFrom; i <= endAt; i++) {
368
+ const page = await state.pdfDoc.getPage(i);
369
+ const viewport = page.getViewport({ scale: 0.2 });
370
+
371
+ const canvas = document.createElement('canvas');
372
+ const ctx = canvas.getContext('2d');
373
+ canvas.height = viewport.height;
374
+ canvas.width = viewport.width;
375
+
376
+ await page.render({
377
+ canvasContext: ctx,
378
+ viewport: viewport
379
+ }).promise;
380
+
381
+ const thumbnailDiv = document.createElement('div');
382
+ thumbnailDiv.className = 'thumbnail-item';
383
+ thumbnailDiv.dataset.page = i;
384
+
385
+ // Create wrapper and label
386
+ const wrapper = document.createElement('div');
387
+ wrapper.className = 'thumbnail-canvas-wrapper';
388
+ wrapper.appendChild(canvas); // Append the actual canvas object with drawing
389
+
390
+ const label = document.createElement('div');
391
+ label.className = 'thumbnail-label';
392
+ label.textContent = `Page ${i}`;
393
+
394
+ thumbnailDiv.appendChild(wrapper);
395
+ thumbnailDiv.appendChild(label);
396
+
397
+ thumbnailDiv.addEventListener('click', () => {
398
+ state.pageNum = i;
399
+ queueRenderPage(i);
400
+ // Highlight selected thumbnail
401
+ updateThumbnailSelection(); // Modified this line
402
+ });
403
+
404
+ thumbnailsContainer.appendChild(thumbnailDiv);
405
+ }
406
+
407
+ // Remove existing "Show More" button if present
408
+ const existingShowMore = thumbnailsContainer.querySelector('.show-more-btn');
409
+ if (existingShowMore) {
410
+ existingShowMore.remove();
411
+ }
412
+
413
+ // Add "Show More" button if there are more pages
414
+ if (endAt < state.pdfDoc.numPages) {
415
+ const showMoreDiv = document.createElement('div');
416
+ showMoreDiv.className = 'show-more-btn';
417
+ showMoreDiv.innerHTML = `
418
+ <button onclick="loadMoreThumbnails()">
419
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
420
+ <path d="M12 5V19M5 12L12 19L19 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
421
+ </svg>
422
+ Show More (${state.pdfDoc.numPages - endAt} remaining)
423
+ </button>
424
+ `;
425
+ thumbnailsContainer.appendChild(showMoreDiv);
426
+ }
427
+
428
+ // Highlight the current page
429
+ updateThumbnailSelection();
430
+ }
431
+
432
+ function loadMoreThumbnails() {
433
+ const nextStart = currentThumbnailCount + 1;
434
+ currentThumbnailCount += thumbnailsPerLoad;
435
+ generateThumbnails(nextStart, thumbnailsPerLoad);
436
+ }
437
+
438
+ function updateThumbnailSelection() {
439
+ document.querySelectorAll('.thumbnail-item').forEach(t => {
440
+ if (parseInt(t.dataset.page) === state.pageNum) {
441
+ t.classList.add('active');
442
+ // Scroll to the active thumbnail if it's not in view
443
+ t.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
444
+ } else {
445
+ t.classList.remove('active');
446
+ }
447
+ });
448
+ }
449
+
450
+ // Responsive adjustments
451
+ window.addEventListener('resize', () => {
452
+ if (window.innerWidth <= 768 && !elements.sidebar.classList.contains('hidden')) {
453
+ elements.sidebar.classList.add('hidden');
454
+ }
455
+ });
456
+
457
+ console.log('PDF Viewer initialized successfully!');
styles.css ADDED
@@ -0,0 +1,812 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ==========================================
2
+ DESIGN SYSTEM - CSS CUSTOM PROPERTIES
3
+ ========================================== */
4
+
5
+ :root {
6
+ /* Dark Mode Colors (Default) */
7
+ --color-bg-primary: #0a0e1a;
8
+ --color-bg-secondary: #111827;
9
+ --color-bg-tertiary: #1a2332;
10
+
11
+ --color-accent-primary: #3b82f6;
12
+ --color-accent-secondary: #2563eb;
13
+ --color-accent-tertiary: #1d4ed8;
14
+
15
+ --color-text-primary: #f9fafb;
16
+ --color-text-secondary: #d1d5db;
17
+ --color-text-muted: #9ca3af;
18
+
19
+ --color-border: rgba(255, 255, 255, 0.1);
20
+ --color-border-hover: rgba(255, 255, 255, 0.2);
21
+
22
+ /* Glassmorphism */
23
+ --glass-bg: rgba(17, 24, 39, 0.7);
24
+ --glass-border: rgba(255, 255, 255, 0.1);
25
+ --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
26
+
27
+ /* Spacing */
28
+ --spacing-xs: 0.25rem;
29
+ --spacing-sm: 0.5rem;
30
+ --spacing-md: 1rem;
31
+ --spacing-lg: 1.5rem;
32
+ --spacing-xl: 2rem;
33
+ --spacing-2xl: 3rem;
34
+
35
+ /* Typography */
36
+ --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
37
+ --font-mono: 'Fira Code', 'Courier New', monospace;
38
+
39
+ /* Transitions */
40
+ --transition-fast: 0.15s ease;
41
+ --transition-normal: 0.3s ease;
42
+ --transition-slow: 0.5s ease;
43
+
44
+ /* Border Radius */
45
+ --radius-sm: 0.375rem;
46
+ --radius-md: 0.5rem;
47
+ --radius-lg: 1rem;
48
+ --radius-xl: 1.5rem;
49
+
50
+ /* Shadows */
51
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
52
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
53
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
54
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
55
+ }
56
+
57
+ /* Light Mode Colors */
58
+ [data-theme="light"] {
59
+ --color-bg-primary: #ffffff;
60
+ --color-bg-secondary: #f9fafb;
61
+ --color-bg-tertiary: #f3f4f6;
62
+
63
+ --color-accent-primary: #3b82f6;
64
+ --color-accent-secondary: #2563eb;
65
+ --color-accent-tertiary: #1d4ed8;
66
+
67
+ --color-text-primary: #111827;
68
+ --color-text-secondary: #4b5563;
69
+ --color-text-muted: #6b7280;
70
+
71
+ --color-border: rgba(0, 0, 0, 0.1);
72
+ --color-border-hover: rgba(0, 0, 0, 0.2);
73
+
74
+ --glass-bg: rgba(255, 255, 255, 0.7);
75
+ --glass-border: rgba(0, 0, 0, 0.1);
76
+ --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
77
+
78
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
79
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
80
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
81
+ --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
82
+ }
83
+
84
+ /* ==========================================
85
+ RESET & BASE STYLES
86
+ ========================================== */
87
+
88
+ * {
89
+ margin: 0;
90
+ padding: 0;
91
+ box-sizing: border-box;
92
+ }
93
+
94
+ html {
95
+ font-size: 16px;
96
+ scroll-behavior: smooth;
97
+ }
98
+
99
+ body {
100
+ font-family: var(--font-primary);
101
+ background-color: var(--color-bg-primary);
102
+ color: var(--color-text-primary);
103
+ line-height: 1.6;
104
+ overflow-x: hidden;
105
+ min-height: 100vh;
106
+ }
107
+
108
+ /* ==========================================
109
+ BACKGROUND EFFECTS
110
+ ========================================== */
111
+
112
+ /* Background effects removed for clean theme switching */
113
+
114
+ /* ==========================================
115
+ HEADER
116
+ ========================================== */
117
+
118
+ .header {
119
+ background: var(--glass-bg);
120
+ backdrop-filter: blur(20px) saturate(180%);
121
+ border-bottom: 1px solid var(--glass-border);
122
+ box-shadow: var(--glass-shadow);
123
+ position: sticky;
124
+ top: 0;
125
+ z-index: 100;
126
+ animation: slideDown 0.5s ease;
127
+ }
128
+
129
+ @keyframes slideDown {
130
+ from {
131
+ transform: translateY(-100%);
132
+ opacity: 0;
133
+ }
134
+
135
+ to {
136
+ transform: translateY(0);
137
+ opacity: 1;
138
+ }
139
+ }
140
+
141
+ .header-content {
142
+ max-width: 1800px;
143
+ margin: 0 auto;
144
+ padding: var(--spacing-lg) var(--spacing-xl);
145
+ display: flex;
146
+ justify-content: space-between;
147
+ align-items: center;
148
+ gap: var(--spacing-lg);
149
+ }
150
+
151
+ .logo-section {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: var(--spacing-md);
155
+ }
156
+
157
+ .logo-icon {
158
+ width: 48px;
159
+ height: 48px;
160
+ background: var(--color-accent-primary);
161
+ border-radius: var(--radius-md);
162
+ display: flex;
163
+ align-items: center;
164
+ justify-content: center;
165
+ color: white;
166
+ box-shadow: var(--shadow-lg);
167
+ transition: all var(--transition-normal);
168
+ }
169
+
170
+ .logo-icon svg {
171
+ width: 28px;
172
+ height: 28px;
173
+ }
174
+
175
+ .logo-icon:hover {
176
+ transform: scale(1.05);
177
+ }
178
+
179
+ @keyframes pulse {
180
+
181
+ 0%,
182
+ 100% {
183
+ transform: scale(1);
184
+ box-shadow: 0 0 0 0 rgba(168, 85, 247, 0.7);
185
+ }
186
+
187
+ 50% {
188
+ transform: scale(1.05);
189
+ box-shadow: 0 0 0 10px rgba(168, 85, 247, 0);
190
+ }
191
+ }
192
+
193
+ .logo-text h1 {
194
+ font-size: 1.5rem;
195
+ font-weight: 800;
196
+ color: var(--color-accent-primary);
197
+ line-height: 1.2;
198
+ }
199
+
200
+ .subtitle {
201
+ font-size: 0.875rem;
202
+ color: var(--color-text-muted);
203
+ font-weight: 400;
204
+ }
205
+
206
+ .header-actions {
207
+ display: flex;
208
+ align-items: center;
209
+ gap: var(--spacing-md);
210
+ }
211
+
212
+ /* ==========================================
213
+ BUTTONS
214
+ ========================================== */
215
+
216
+ .btn-primary {
217
+ background: var(--color-accent-primary);
218
+ color: white;
219
+ border: none;
220
+ padding: 0.75rem 1.5rem;
221
+ border-radius: var(--radius-md);
222
+ font-weight: 600;
223
+ font-size: 0.875rem;
224
+ cursor: pointer;
225
+ transition: all var(--transition-normal);
226
+ box-shadow: var(--shadow-md);
227
+ display: flex;
228
+ align-items: center;
229
+ gap: var(--spacing-sm);
230
+ text-decoration: none;
231
+ font-family: var(--font-primary);
232
+ }
233
+
234
+ .btn-primary:hover {
235
+ background: var(--color-accent-secondary);
236
+ transform: translateY(-2px);
237
+ box-shadow: var(--shadow-xl);
238
+ }
239
+
240
+ .btn-primary:active {
241
+ transform: translateY(0);
242
+ }
243
+
244
+ .btn-primary svg {
245
+ width: 18px;
246
+ height: 18px;
247
+ }
248
+
249
+ .btn-secondary {
250
+ background: var(--color-bg-tertiary);
251
+ color: var(--color-text-primary);
252
+ border: 1px solid var(--color-border);
253
+ padding: 0.5rem 1rem;
254
+ border-radius: var(--radius-md);
255
+ font-weight: 500;
256
+ font-size: 0.875rem;
257
+ cursor: pointer;
258
+ transition: all var(--transition-fast);
259
+ font-family: var(--font-primary);
260
+ }
261
+
262
+ .btn-secondary:hover {
263
+ background: var(--color-bg-secondary);
264
+ border-color: var(--color-border-hover);
265
+ }
266
+
267
+ .btn-icon {
268
+ background: transparent;
269
+ color: var(--color-text-secondary);
270
+ border: 1px solid var(--color-border);
271
+ width: 40px;
272
+ height: 40px;
273
+ border-radius: var(--radius-md);
274
+ cursor: pointer;
275
+ transition: all var(--transition-fast);
276
+ display: flex;
277
+ align-items: center;
278
+ justify-content: center;
279
+ }
280
+
281
+ .btn-icon:hover {
282
+ background: var(--color-bg-tertiary);
283
+ border-color: var(--color-border-hover);
284
+ color: var(--color-text-primary);
285
+ transform: scale(1.05);
286
+ }
287
+
288
+ .btn-icon svg {
289
+ width: 20px;
290
+ height: 20px;
291
+ }
292
+
293
+ /* ==========================================
294
+ MAIN CONTAINER
295
+ ========================================== */
296
+
297
+ .main-container {
298
+ display: flex;
299
+ height: calc(100vh - 88px);
300
+ max-width: 1800px;
301
+ margin: 0 auto;
302
+ gap: var(--spacing-lg);
303
+ padding: var(--spacing-lg);
304
+ }
305
+
306
+ /* ==========================================
307
+ SIDEBAR
308
+ ========================================== */
309
+
310
+ .sidebar {
311
+ width: 320px;
312
+ background: var(--glass-bg);
313
+ backdrop-filter: blur(20px) saturate(180%);
314
+ border: 1px solid var(--glass-border);
315
+ border-radius: var(--radius-lg);
316
+ padding: var(--spacing-lg);
317
+ overflow-y: auto;
318
+ box-shadow: var(--glass-shadow);
319
+ animation: slideInLeft 0.5s ease;
320
+ transition: transform var(--transition-normal);
321
+ }
322
+
323
+ @keyframes slideInLeft {
324
+ from {
325
+ transform: translateX(-100%);
326
+ opacity: 0;
327
+ }
328
+
329
+ to {
330
+ transform: translateX(0);
331
+ opacity: 1;
332
+ }
333
+ }
334
+
335
+ .sidebar.hidden {
336
+ transform: translateX(-100%);
337
+ position: absolute;
338
+ left: 0;
339
+ height: calc(100vh - 120px);
340
+ }
341
+
342
+ .sidebar-header {
343
+ display: flex;
344
+ justify-content: space-between;
345
+ align-items: center;
346
+ margin-bottom: var(--spacing-lg);
347
+ }
348
+
349
+ .sidebar-header h2 {
350
+ font-size: 1.25rem;
351
+ font-weight: 700;
352
+ }
353
+
354
+ .page-info {
355
+ display: grid;
356
+ grid-template-columns: 1fr 1fr;
357
+ gap: var(--spacing-md);
358
+ margin-bottom: var(--spacing-lg);
359
+ }
360
+
361
+ .info-item {
362
+ background: var(--color-bg-tertiary);
363
+ border: 1px solid var(--color-border);
364
+ border-radius: var(--radius-md);
365
+ padding: var(--spacing-md);
366
+ text-align: center;
367
+ }
368
+
369
+ .info-label {
370
+ display: block;
371
+ font-size: 0.75rem;
372
+ color: var(--color-text-muted);
373
+ margin-bottom: var(--spacing-xs);
374
+ text-transform: uppercase;
375
+ letter-spacing: 0.05em;
376
+ }
377
+
378
+ .info-value {
379
+ display: block;
380
+ font-size: 1.5rem;
381
+ font-weight: 700;
382
+ color: var(--color-accent-primary);
383
+ }
384
+
385
+ .page-navigation {
386
+ margin-bottom: var(--spacing-lg);
387
+ }
388
+
389
+ .page-navigation label {
390
+ display: block;
391
+ font-size: 0.875rem;
392
+ font-weight: 600;
393
+ margin-bottom: var(--spacing-sm);
394
+ color: var(--color-text-secondary);
395
+ }
396
+
397
+ .page-input-group {
398
+ display: flex;
399
+ gap: var(--spacing-sm);
400
+ }
401
+
402
+ .page-input-group input {
403
+ flex: 1;
404
+ background: var(--color-bg-tertiary);
405
+ border: 1px solid var(--color-border);
406
+ border-radius: var(--radius-md);
407
+ padding: 0.5rem;
408
+ color: var(--color-text-primary);
409
+ font-size: 0.875rem;
410
+ font-family: var(--font-primary);
411
+ }
412
+
413
+ .page-input-group input:focus {
414
+ outline: none;
415
+ border-color: var(--color-accent-primary);
416
+ }
417
+
418
+ .thumbnails {
419
+ display: flex;
420
+ flex-direction: column;
421
+ gap: var(--spacing-sm);
422
+ }
423
+
424
+ .thumbnail-item {
425
+ cursor: pointer;
426
+ padding: var(--spacing-sm);
427
+ border: 2px solid var(--color-border);
428
+ border-radius: var(--radius-md);
429
+ background: var(--color-bg-tertiary);
430
+ transition: all var(--transition-fast);
431
+ text-align: center;
432
+ }
433
+
434
+ .thumbnail-item:hover {
435
+ border-color: var(--color-accent-primary);
436
+ transform: scale(1.02);
437
+ box-shadow: var(--shadow-md);
438
+ }
439
+
440
+ .thumbnail-item.active {
441
+ border-color: var(--color-accent-primary);
442
+ background: var(--color-accent-primary);
443
+ background: linear-gradient(to bottom, var(--color-accent-primary) 0%, var(--color-accent-primary) 4px, var(--color-bg-tertiary) 4px);
444
+ }
445
+
446
+ .thumbnail-canvas-wrapper {
447
+ margin-bottom: var(--spacing-xs);
448
+ border-radius: var(--radius-sm);
449
+ overflow: hidden;
450
+ background: white;
451
+ }
452
+
453
+ .thumbnail-canvas-wrapper canvas {
454
+ width: 100%;
455
+ height: auto;
456
+ display: block;
457
+ }
458
+
459
+ .thumbnail-label {
460
+ font-size: 0.75rem;
461
+ color: var(--color-text-muted);
462
+ font-weight: 500;
463
+ }
464
+
465
+ .thumbnail-item.active .thumbnail-label {
466
+ color: var(--color-accent-primary);
467
+ font-weight: 600;
468
+ }
469
+
470
+ .show-more-btn {
471
+ text-align: center;
472
+ padding: var(--spacing-md) 0;
473
+ }
474
+
475
+ .show-more-btn button {
476
+ background: var(--color-bg-tertiary);
477
+ border: 1px solid var(--color-border);
478
+ color: var(--color-text-secondary);
479
+ padding: var(--spacing-sm) var(--spacing-md);
480
+ border-radius: var(--radius-md);
481
+ font-size: 0.875rem;
482
+ font-weight: 500;
483
+ cursor: pointer;
484
+ transition: all var(--transition-fast);
485
+ display: flex;
486
+ align-items: center;
487
+ gap: var(--spacing-sm);
488
+ margin: 0 auto;
489
+ font-family: var(--font-primary);
490
+ }
491
+
492
+ .show-more-btn button:hover {
493
+ background: var(--color-accent-primary);
494
+ color: white;
495
+ border-color: var(--color-accent-primary);
496
+ transform: translateY(-2px);
497
+ box-shadow: var(--shadow-md);
498
+ }
499
+
500
+ .show-more-btn button svg {
501
+ width: 16px;
502
+ height: 16px;
503
+ }
504
+
505
+ .loading-text {
506
+ color: var(--color-text-muted);
507
+ font-size: 0.875rem;
508
+ text-align: center;
509
+ padding: var(--spacing-lg);
510
+ }
511
+
512
+ /* ==========================================
513
+ VIEWER CONTAINER
514
+ ========================================== */
515
+
516
+ .viewer-container {
517
+ flex: 1;
518
+ display: flex;
519
+ flex-direction: column;
520
+ background: var(--glass-bg);
521
+ backdrop-filter: blur(20px) saturate(180%);
522
+ border: 1px solid var(--glass-border);
523
+ border-radius: var(--radius-lg);
524
+ overflow: hidden;
525
+ box-shadow: var(--glass-shadow);
526
+ position: relative;
527
+ animation: fadeIn 0.5s ease;
528
+ }
529
+
530
+ @keyframes fadeIn {
531
+ from {
532
+ opacity: 0;
533
+ transform: scale(0.95);
534
+ }
535
+
536
+ to {
537
+ opacity: 1;
538
+ transform: scale(1);
539
+ }
540
+ }
541
+
542
+ .sidebar-toggle {
543
+ position: absolute;
544
+ top: var(--spacing-md);
545
+ left: var(--spacing-md);
546
+ z-index: 10;
547
+ background: var(--glass-bg);
548
+ backdrop-filter: blur(20px);
549
+ color: var(--color-text-primary);
550
+ border: 1px solid var(--glass-border);
551
+ width: 44px;
552
+ height: 44px;
553
+ border-radius: var(--radius-md);
554
+ cursor: pointer;
555
+ transition: all var(--transition-fast);
556
+ display: flex;
557
+ align-items: center;
558
+ justify-content: center;
559
+ box-shadow: var(--shadow-md);
560
+ }
561
+
562
+ .sidebar-toggle:hover {
563
+ background: var(--color-bg-tertiary);
564
+ transform: scale(1.05);
565
+ }
566
+
567
+ .sidebar-toggle svg {
568
+ width: 22px;
569
+ height: 22px;
570
+ }
571
+
572
+ /* ==========================================
573
+ SEARCH PANEL
574
+ ========================================== */
575
+
576
+ .search-panel {
577
+ background: var(--color-bg-secondary);
578
+ border-bottom: 1px solid var(--color-border);
579
+ padding: var(--spacing-md);
580
+ display: none;
581
+ animation: slideDown 0.3s ease;
582
+ }
583
+
584
+ .search-panel.active {
585
+ display: block;
586
+ }
587
+
588
+ .search-input-group {
589
+ display: flex;
590
+ gap: var(--spacing-sm);
591
+ align-items: center;
592
+ margin-bottom: var(--spacing-sm);
593
+ }
594
+
595
+ .search-input-group input {
596
+ flex: 1;
597
+ background: var(--color-bg-tertiary);
598
+ border: 1px solid var(--color-border);
599
+ border-radius: var(--radius-md);
600
+ padding: 0.5rem 1rem;
601
+ color: var(--color-text-primary);
602
+ font-size: 0.875rem;
603
+ font-family: var(--font-primary);
604
+ }
605
+
606
+ .search-input-group input:focus {
607
+ outline: none;
608
+ border-color: var(--color-accent-primary);
609
+ box-shadow: 0 0 0 3px rgba(168, 85, 247, 0.1);
610
+ }
611
+
612
+ .search-results {
613
+ font-size: 0.75rem;
614
+ color: var(--color-text-muted);
615
+ text-align: center;
616
+ }
617
+
618
+ /* ==========================================
619
+ CONTROLS
620
+ ========================================== */
621
+
622
+ .controls {
623
+ background: var(--color-bg-secondary);
624
+ border-bottom: 1px solid var(--color-border);
625
+ padding: var(--spacing-md);
626
+ display: flex;
627
+ justify-content: center;
628
+ align-items: center;
629
+ gap: var(--spacing-xl);
630
+ }
631
+
632
+ .control-group {
633
+ display: flex;
634
+ align-items: center;
635
+ gap: var(--spacing-md);
636
+ }
637
+
638
+ .page-indicator,
639
+ .zoom-level {
640
+ font-size: 0.875rem;
641
+ font-weight: 600;
642
+ color: var(--color-text-secondary);
643
+ min-width: 80px;
644
+ text-align: center;
645
+ font-family: var(--font-mono);
646
+ }
647
+
648
+ /* ==========================================
649
+ CANVAS CONTAINER
650
+ ========================================== */
651
+
652
+ .canvas-container {
653
+ flex: 1;
654
+ overflow: auto;
655
+ display: flex;
656
+ align-items: center;
657
+ justify-content: center;
658
+ padding: var(--spacing-lg);
659
+ position: relative;
660
+ background: var(--color-bg-primary);
661
+ }
662
+
663
+ .canvas-container::-webkit-scrollbar {
664
+ width: 12px;
665
+ height: 12px;
666
+ }
667
+
668
+ .canvas-container::-webkit-scrollbar-track {
669
+ background: var(--color-bg-secondary);
670
+ }
671
+
672
+ .canvas-container::-webkit-scrollbar-thumb {
673
+ background: var(--color-bg-tertiary);
674
+ border-radius: var(--radius-sm);
675
+ border: 2px solid var(--color-bg-secondary);
676
+ }
677
+
678
+ .canvas-container::-webkit-scrollbar-thumb:hover {
679
+ background: var(--color-accent-primary);
680
+ }
681
+
682
+ #pdfCanvas {
683
+ box-shadow: var(--shadow-xl);
684
+ max-width: 100%;
685
+ height: auto;
686
+ border-radius: var(--radius-sm);
687
+ }
688
+
689
+ /* ==========================================
690
+ LOADING OVERLAY
691
+ ========================================== */
692
+
693
+ .loading-overlay {
694
+ position: absolute;
695
+ top: 0;
696
+ left: 0;
697
+ right: 0;
698
+ bottom: 0;
699
+ background: var(--color-bg-primary);
700
+ display: flex;
701
+ flex-direction: column;
702
+ align-items: center;
703
+ justify-content: center;
704
+ gap: var(--spacing-lg);
705
+ z-index: 50;
706
+ }
707
+
708
+ .loading-overlay.hidden {
709
+ display: none;
710
+ }
711
+
712
+ .spinner {
713
+ width: 60px;
714
+ height: 60px;
715
+ border: 4px solid var(--color-border);
716
+ border-top-color: var(--color-accent-primary);
717
+ border-radius: 50%;
718
+ animation: spin 1s linear infinite;
719
+ }
720
+
721
+ @keyframes spin {
722
+ to {
723
+ transform: rotate(360deg);
724
+ }
725
+ }
726
+
727
+ .loading-overlay p {
728
+ color: var(--color-text-secondary);
729
+ font-size: 1rem;
730
+ font-weight: 500;
731
+ }
732
+
733
+ /* ==========================================
734
+ RESPONSIVE DESIGN
735
+ ========================================== */
736
+
737
+ @media (max-width: 1024px) {
738
+ .header-content {
739
+ padding: var(--spacing-md);
740
+ }
741
+
742
+ .logo-text h1 {
743
+ font-size: 1.25rem;
744
+ }
745
+
746
+ .main-container {
747
+ padding: var(--spacing-md);
748
+ gap: var(--spacing-md);
749
+ }
750
+
751
+ .sidebar {
752
+ width: 280px;
753
+ }
754
+ }
755
+
756
+ @media (max-width: 768px) {
757
+ .header-content {
758
+ flex-direction: column;
759
+ gap: var(--spacing-md);
760
+ }
761
+
762
+ .header-actions {
763
+ width: 100%;
764
+ justify-content: space-between;
765
+ }
766
+
767
+ .btn-primary span {
768
+ display: none;
769
+ }
770
+
771
+ .main-container {
772
+ flex-direction: column;
773
+ height: auto;
774
+ }
775
+
776
+ .sidebar {
777
+ width: 100%;
778
+ max-height: 300px;
779
+ }
780
+
781
+ .sidebar.hidden {
782
+ display: none;
783
+ }
784
+
785
+ .controls {
786
+ flex-direction: column;
787
+ gap: var(--spacing-md);
788
+ }
789
+
790
+ .canvas-container {
791
+ min-height: 500px;
792
+ }
793
+ }
794
+
795
+ @media (max-width: 480px) {
796
+ .logo-text h1 {
797
+ font-size: 1rem;
798
+ }
799
+
800
+ .subtitle {
801
+ font-size: 0.75rem;
802
+ }
803
+
804
+ .btn-icon {
805
+ width: 36px;
806
+ height: 36px;
807
+ }
808
+
809
+ .control-group {
810
+ gap: var(--spacing-sm);
811
+ }
812
+ }