Compare commits

...
19 Commits
Author SHA1 Message Date
abdullahalam123 9bd193cd9d Release v1.2.0 2025-11-07 11:11:49 +05:30
abdullahalam123 b8cd43c9ec Merge branch 'bookmarks' 2025-11-07 11:11:10 +05:30
abdullahalam123 400ec9aadf style(ui): update color scheme and fix styling inconsistencies
- Change grey to gray for consistency in CSS class names
- Update page title and add favicon
- Improve button and background colors for better contrast
- Fix mobile menu script path
2025-11-07 11:10:22 +05:30
abdullahalam123 f40108627e feat(pdf-viewer): add zoom controls and improve destination picking
- Add zoom in/out/fit buttons with indicator
- Improve destination picking with coordinate display and PDF coordinate conversion
- Enhance page rendering with device pixel ratio support
- Add visual feedback for bookmark destinations
2025-11-07 10:46:39 +05:30
abdullahalam123 e64215a022 feat(bookmark): improve PDF bookmark extraction logic
enhance named destinations handling to support full name-tree traversal
add fallback page index resolution for better compatibility
fix parent-child relationship handling for zero-page bookmarks
2025-11-07 09:31:57 +05:30
Alam af75a3d74f Update SIMPLE_MODE.md 2025-10-29 16:37:31 +05:30
abdullahalam123 ad49adb744 feat(bookmark-pdf): improve bookmark extraction with reference resolution
add helper functions to resolve PDF object references and handle named destinations
implement more robust page index finding and destination resolution
refactor traversal logic to use resolved references consistently
2025-10-28 01:24:34 +05:30
Alam 392b810c54 Merge pull request #115 from AltuisticIsopod/main
Add GHCR publishing support
2025-10-28 00:23:21 +05:30
abdullahalam123 090146c600 feat(bookmark): improve pdf bookmark tool ui with dark mode and drag-drop
- Add dark mode styling to the bookmark tool page
- Implement drag-and-drop file upload zones for PDF, CSV and JSON
- Enhance file input UI with icons and better visual feedback
- Fix formatting and whitespace issues in multiple files
2025-10-27 22:35:49 +05:30
abdullahalam123 72e74d2a62 feat(pdf-tools): add bookmark pdf tool and lucide icons integration
- Add 'bookmark-pdf' to singlePdfLoadTools
- Create new lucide-init.ts for icon initialization
- Update HTML files to use local lucide initialization
- Add bookmark.html page with full bookmark editor functionality
- Update tools config to include bookmark tool
- Modify main.ts to handle tool cards with href links
2025-10-27 22:01:36 +05:30
Lalit Sudhir c49957b721 Add GHCR publishing support 2025-10-27 00:31:18 -07:00
abdullahalam123 72688cde4c Release v1.1.5 2025-10-25 15:05:42 +05:30
abdullahalam123 da6610fa65 docs: update GitHub repo URL and improve security docs formatting
Update the GitHub repository URL in release.js to point to the correct organization.
Improve formatting in SECURITY.md by adding newlines before code blocks and fixing YAML indentation for better readability.
Implement non root user for running BentoPDF
2025-10-25 15:05:36 +05:30
Alam e89dd5e116 Merge pull request #95 from AltuisticIsopod/main
feat: implement non-root user security for Docker container
2025-10-25 14:49:18 +05:30
Alam 005ff78f6a Add video tutorial link for Docker setup
Added a link to a video tutorial for Docker setup.
2025-10-25 14:48:37 +05:30
Lalit Sudhir 24b5204098 standardize port mapping to 8080 across all Docker configurations 2025-10-25 00:53:18 -07:00
Lalit Sudhir 874d97889f optimize Dockerfile and update nginx base image 2025-10-24 00:36:40 -07:00
Lalit Sudhir 922e42548f migrate to nginx-unprivileged for enhanced security 2025-10-23 19:42:34 -07:00
Lalit Sudhir 8ac62b48a6 feat: implement non-root user security for Docker container 2025-10-22 19:10:33 -07:00
24 changed files with 3311 additions and 61 deletions
+9
View File
@@ -33,6 +33,12 @@ jobs:
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version from tag
id: version
run: |
@@ -55,6 +61,9 @@ jobs:
bentopdf/bentopdf${{ matrix.mode.suffix }}:latest
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }}
bentopdf/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:latest
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version_without_v }}
ghcr.io/${{ github.repository_owner }}/bentopdf${{ matrix.mode.suffix }}:${{ steps.version.outputs.version }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
+33 -8
View File
@@ -1,11 +1,8 @@
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# Build without type checking (vite build only)
@@ -15,11 +12,39 @@ ENV SIMPLE_MODE=$SIMPLE_MODE
RUN npm run build -- --mode production
# Production stage
FROM nginx:alpine
FROM nginxinc/nginx-unprivileged:stable-alpine-slim
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
COPY --chown=nginx:nginx --from=builder /app/dist /usr/share/nginx/html
COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf
RUN mkdir -p /etc/nginx/tmp && chown -R nginx:nginx /etc/nginx/tmp
EXPOSE 80
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]
CMD ["nginx", "-g", "daemon off;"]
# Old Dockerfile for Root User
# # Build stage
# FROM node:20-alpine AS builder
# WORKDIR /app
# COPY package*.json ./
# RUN npm ci
# COPY . .
# # Build without type checking (vite build only)
# # Pass SIMPLE_MODE environment variable if provided
# ARG SIMPLE_MODE=false
# ENV SIMPLE_MODE=$SIMPLE_MODE
# RUN npm run build -- --mode production
# # Production stage
# FROM nginx:alpine
# COPY --from=builder /app/dist /usr/share/nginx/html
# COPY nginx.conf /etc/nginx/nginx.conf
# EXPOSE 8080
# CMD ["nginx", "-g", "daemon off;"]
+36 -4
View File
@@ -102,10 +102,19 @@ You can run BentoPDF locally for development or personal use.
### 🚀 Quick Start with Docker
You can run BentoPDF directly from Docker Hub without cloning the repository:
You can run BentoPDF directly from Docker Hub or GitHub Container Registry without cloning the repository:
You can also watch the video on how to set it up 👉
[BentoPDF Docker Setup](https://drive.google.com/file/d/1C4eJ2nqeaH__1Tlad-xuBHaF2Ha4fSBf/view?usp=drive_link)
**Using Docker Hub:**
```bash
docker run -p 3000:80 bentopdf/bentopdf:latest
docker run -p 3000:8080 bentopdf/bentopdf:latest
```
**Using GitHub Container Registry:**
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf:latest
```
Open your browser at: http://localhost:3000
@@ -124,7 +133,7 @@ services:
image: bentopdf/bentopdf:latest
container_name: bentopdf
ports:
- '3000:80'
- '3000:8080'
restart: unless-stopped
```
@@ -149,14 +158,37 @@ For organizations that want a clean, distraction-free interface focused solely o
For more details, see [SIMPLE_MODE.md](SIMPLE_MODE.md).
### 🔒 Security Features
BentoPDF runs as a non-root user using nginx-unprivileged for enhanced security:
- **Non-Root Execution**: Container runs with minimal privileges using nginx-unprivileged
- **Port 8080**: Uses high port number to avoid requiring root privileges
- **Security Best Practices**: Follows Principle of Least Privilege
#### Basic Usage
```bash
docker build -t bentopdf .
docker run -p 8080:8080 bentopdf
```
For detailed security configuration, see [SECURITY.md](SECURITY.md).
### 📦 Version Management
BentoPDF supports semantic versioning with multiple Docker tags:
BentoPDF supports semantic versioning with multiple Docker tags available on both Docker Hub and GitHub Container Registry:
**Docker Hub:**
- **Latest**: `bentopdf/bentopdf:latest`
- **Specific Version**: `bentopdf/bentopdf:1.0.0`
- **Version with Prefix**: `bentopdf/bentopdf:v1.0.0`
**GitHub Container Registry:**
- **Latest**: `ghcr.io/alam00000/bentopdf:latest`
- **Specific Version**: `ghcr.io/alam00000/bentopdf:1.0.0`
- **Version with Prefix**: `ghcr.io/alam00000/bentopdf:v1.0.0`
#### Quick Release
```bash
+110
View File
@@ -0,0 +1,110 @@
# Security Configuration
## Non-Root User Support
BentoPDF now uses nginx-unprivileged for enhanced security. This follows the Principle of Least Privilege and is essential for production environments.
### Security Benefits
- **Reduced Attack Surface**: If compromised, attackers won't have root privileges
- **Compliance**: Meets security standards like SOC 2, PCI DSS
- **Kubernetes/OpenShift Compatibility**: Works with security policies that require non-root execution
- **System Protection**: Prevents system-wide damage if the application is compromised
### Usage
#### Default Configuration (nginx-unprivileged)
```bash
docker build -t bentopdf .
docker run -p 8080:8080 bentopdf
```
#### Simple Mode
```bash
# Build with simple mode enabled
docker build --build-arg SIMPLE_MODE=true -t bentopdf-simple .
# Run the container
docker run -p 8080:8080 bentopdf-simple
```
#### Kubernetes Example
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: bentopdf
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 2000
runAsGroup: 2000
containers:
- name: bentopdf
image: bentopdf:latest
ports:
- containerPort: 8080
```
#### Docker Compose Example
```yaml
version: '3.8'
services:
bentopdf:
build:
context: .
dockerfile: Dockerfile
args:
SIMPLE_MODE: false
ports:
- '8080:8080'
security_opt:
- no-new-privileges:true
```
### Verification
To verify the container is running as non-root:
```bash
# Check the user inside the container
docker exec <container_id> whoami
# Should output: nginx
# Check the user ID
docker exec <container_id> id
# Should show UID/GID for nginx user (typically 101)
```
### Security Best Practices
1. **Use nginx-unprivileged**: Built-in non-root user with minimal privileges
2. **Regular Updates**: Keep the base image updated (currently using 1.29-alpine)
3. **Port 8080**: Use high port numbers to avoid requiring root privileges
4. **Security Scanning**: Regularly scan images for vulnerabilities
5. **Network Policies**: Implement network segmentation
### Troubleshooting
If you encounter permission issues:
1. **Check file ownership**: Ensure all application files are owned by the nginx user
2. **Verify PID directory**: Ensure `/etc/nginx/tmp/` directory exists and is writable
3. **Port binding**: Ensure port 8080 is available and not blocked by firewall
### Migration from Root
If migrating from a root-based setup:
1. Update your Dockerfile to use nginx-unprivileged base image
2. Change port mappings from 80 to 8080 in all configurations
3. Update nginx.conf to use `/etc/nginx/tmp/nginx.pid` for PID file
4. Rebuild your images with the new security settings
5. Update your deployment configurations (Kubernetes, Docker Compose, etc.)
6. Test thoroughly in a staging environment
+24 -6
View File
@@ -23,20 +23,28 @@ When enabled, Simple Mode will:
Use the pre-built Simple Mode image directly:
**Using Docker Hub:**
```bash
docker run -p 3000:80 bentopdf/bentopdf-simple:latest
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest
```
**Using GitHub Container Registry:**
```bash
docker run -p 3000:8080 ghcr.io/alam00000/bentopdf-simple:latest
```
Or with Docker Compose:
```yaml
services:
bentopdf:
# Using Docker Hub
image: bentopdf/bentopdf-simple:latest
# Or using GitHub Container Registry
# image: ghcr.io/alam00000/bentopdf-simple:latest
container_name: bentopdf
restart: unless-stopped
ports:
- '3000:80'
- '3000:8080'
```
### Method 2: Using Docker Compose with Build
@@ -54,7 +62,7 @@ Build the image with the SIMPLE_MODE build argument:
```bash
docker build --build-arg SIMPLE_MODE=true -t bentopdf-simple .
docker run -p 3000:80 bentopdf-simple
docker run -p 3000:8080 bentopdf-simple
```
### Method 4: Using npm Script (Easiest for Local Development)
@@ -96,7 +104,7 @@ This automatically builds and serves Simple Mode on `http://localhost:3000`.
```bash
# Pull and run the Simple Mode image
docker pull bentopdf/bentopdf-simple:latest
docker run -p 3000:80 bentopdf/bentopdf-simple:latest
docker run -p 3000:8080 bentopdf/bentopdf-simple:latest
```
Open `http://localhost:3000` in your browser.
@@ -117,10 +125,10 @@ Open `http://localhost:3000` in your browser.
```bash
# Test Normal Mode
docker run -p 3000:80 bentopdf/bentopdf:latest
docker run -p 3000:8080 bentopdf/bentopdf:latest
# Test Simple Mode
docker run -p 3001:80 bentopdf/bentopdf-simple:latest
docker run -p 3001:8080 bentopdf/bentopdf-simple:latest
```
- Normal Mode: `http://localhost:3000`
@@ -142,14 +150,24 @@ When Simple Mode is working correctly, you should see:
### Normal Mode (Full Branding)
**Docker Hub:**
- `bentopdf/bentopdf:latest`
- `bentopdf/bentopdf:v1.0.0` (versioned)
**GitHub Container Registry:**
- `ghcr.io/alam00000/bentopdf:latest`
- `ghcr.io/alam00000/bentopdf:v1.0.0` (versioned)
### Simple Mode (Clean Interface)
**Docker Hub:**
- `bentopdf/bentopdf-simple:latest`
- `bentopdf/bentopdf-simple:v1.0.0` (versioned)
**GitHub Container Registry:**
- `ghcr.io/alam00000/bentopdf-simple:latest`
- `ghcr.io/alam00000/bentopdf-simple:v1.0.0` (versioned)
## 🚀 Production Deployment Examples
### Internal Company Tool
+2 -7
View File
@@ -4,10 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>About Bentopdf - Fast, Private, and Free PDF Tools</title>
<script src="https://unpkg.com/lucide@latest"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="icon" type="image/png" href="images/favicon.svg" />
<link href="/src/css/styles.css" rel="stylesheet" />
</head>
@@ -343,9 +339,8 @@
</div>
</footer>
<script>
lucide.createIcons();
</script>
<script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script>
</body>
</html>
+2 -5
View File
@@ -4,8 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Contact Us - BentoPDF</title>
<script src="https://unpkg.com/lucide@latest"></script>
<link rel="icon" type="image/png" href="images/favicon.svg" />
<link href="/src/css/styles.css" rel="stylesheet" />
</head>
@@ -208,9 +206,8 @@
</div>
</footer>
<script>
lucide.createIcons();
</script>
<script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script>
</body>
</html>
+3 -1
View File
@@ -3,7 +3,9 @@ services:
build:
context: .
dockerfile: Dockerfile
args:
SIMPLE_MODE: false # false for default mode, true for simple mode
container_name: bentopdf
ports:
- '3000:80'
- '8080:8080'
restart: unless-stopped
+1 -1
View File
@@ -6,4 +6,4 @@ services:
container_name: bentopdf
restart: unless-stopped
ports:
- '3000:80'
- '8080:8080'
+2 -8
View File
@@ -4,11 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Frequently Asked Questions - BentoPDF</title>
<script src="https://unpkg.com/lucide@latest"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="icon" type="image/png" href="images/favicon.svg" />
<link href="/src/css/styles.css" rel="stylesheet" />
</head>
@@ -396,9 +391,8 @@
</div>
</footer>
<script>
lucide.createIcons();
</script>
<script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script>
</body>
</html>
+1
View File
@@ -915,6 +915,7 @@
</div>
</footer>
<script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/js/main.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script>
</body>
+3 -1
View File
@@ -1,3 +1,5 @@
pid /etc/nginx/tmp/nginx.pid;
events {
worker_connections 1024;
}
@@ -15,7 +17,7 @@ http {
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
server {
listen 80;
listen 8080;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "bento-pdf",
"private": true,
"version": "1.1.4",
"version": "1.2.0",
"license": "Apache-2.0",
"type": "module",
"scripts": {
+2 -7
View File
@@ -4,10 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Privacy Policy - BentoPDF</title>
<script src="https://unpkg.com/lucide@latest"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="icon" type="image/png" href="images/favicon.svg" />
<link href="/src/css/styles.css" rel="stylesheet" />
</head>
@@ -318,9 +314,8 @@
</div>
</div>
</footer>
<script>
lucide.createIcons();
</script>
<script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script>
</body>
</html>
+1 -1
View File
@@ -91,7 +91,7 @@ function main() {
console.log(`🎉 Release v${newVersion} complete!`);
console.log(`📦 Docker image: bentopdf/bentopdf:${newVersion}`);
console.log(
`🏷️ GitHub release: https://github.com/AltuisticIsopod/bentopdf/releases/tag/${tagName}`
`🏷️ GitHub release: https://github.com/alam00000/bentopdf/releases/tag/${tagName}`
);
}
+1
View File
@@ -40,6 +40,7 @@ export const singlePdfLoadTools = [
'add-attachments',
'sanitize-pdf',
'remove-restrictions',
'bookmark-pdf',
];
export const simpleTools = [
+7 -1
View File
@@ -76,7 +76,13 @@ export const categories = [
subtitle:
'Annotate, highlight, redact, comment, add shapes/images, search, and view PDFs.',
},
// { id: 'crop', name: 'Crop PDF', icon: 'crop', subtitle: 'Trim the margins of every page in your PDF.' },
{
// id: 'bookmark-pdf',
href: '/src/pages/bookmark.html',
name: 'Edit Bookmarks',
icon: 'bookmark',
subtitle: 'Add, edit, import, delete and extract PDF bookmarks.',
},
{
id: 'add-page-numbers',
name: 'Page Numbers',
File diff suppressed because it is too large Load Diff
+13 -4
View File
@@ -137,10 +137,19 @@ const init = () => {
'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 md:gap-6';
category.tools.forEach((tool) => {
const toolCard = document.createElement('div');
toolCard.className =
'tool-card bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center';
toolCard.dataset.toolId = tool.id;
let toolCard: HTMLDivElement | HTMLAnchorElement;
if (tool.href) {
toolCard = document.createElement('a');
toolCard.href = tool.href;
toolCard.className =
'tool-card block bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center no-underline hover:shadow-lg transition duration-200';
} else {
toolCard = document.createElement('div');
toolCard.className =
'tool-card bg-gray-800 rounded-xl p-4 cursor-pointer flex flex-col items-center justify-center text-center hover:shadow-lg transition duration-200';
toolCard.dataset.toolId = tool.id;
}
const icon = document.createElement('i');
icon.className = 'w-10 h-10 mb-3 text-indigo-400';
+10
View File
@@ -1,5 +1,6 @@
import createModule from '@neslinesli93/qpdf-wasm';
import { showLoader, hideLoader, showAlert } from '../ui';
import { createIcons } from 'lucide';
const STANDARD_SIZES = {
A4: { width: 595.28, height: 841.89 },
@@ -177,3 +178,12 @@ export async function initializeQpdf() {
return qpdfInstance;
}
export function initializeIcons(): void {
createIcons({
attrs: {
class: 'bento-icon',
'stroke-width': '1.5',
},
});
}
+5
View File
@@ -0,0 +1,5 @@
import { createIcons, icons } from 'lucide';
document.addEventListener('DOMContentLoaded', () => {
createIcons({ icons });
});
+867
View File
@@ -0,0 +1,867 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Advanced PDF Bookmark Tool - BentoPDF</title>
<link rel="icon" type="image/png" href="../../images/favicon.svg" />
<link href="../../src/css/styles.css" rel="stylesheet" />
<style>
body {
color: #374151;
}
.sortable-ghost {
opacity: 0.4;
border: 2px dashed #2b7fff;
}
.sortable-drag {
cursor: move;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal-content {
background: white;
border-radius: 0.5rem;
max-width: 500px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
}
.modal-minimized {
position: fixed;
top: 1rem;
right: 1rem;
max-width: 350px;
max-height: none;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
z-index: 1001;
}
.child-container {
position: relative;
padding-left: 1.5rem;
margin-top: 0.5rem;
}
.child-container::before {
content: '';
position: absolute;
left: 0.75rem;
top: 0;
bottom: 0;
width: 2px;
background: #e5e7eb;
}
.child-container > li {
position: relative;
}
.child-container > li::before {
content: '';
position: absolute;
left: -0.75rem;
top: 1.25rem;
width: 0.75rem;
height: 2px;
background: #e5e7eb;
}
.style-preview {
padding: 0.5rem;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
min-height: 2.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.destination-marker {
position: absolute;
width: 30px;
height: 30px;
margin-left: -15px;
margin-top: -15px;
pointer-events: none;
z-index: 10;
}
.destination-marker svg {
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
}
.pdf-canvas-wrapper {
position: relative;
display: inline-block;
}
.pdf-canvas-wrapper.picking-mode {
cursor: crosshair;
}
.coordinate-tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
pointer-events: none;
z-index: 20;
white-space: nowrap;
}
.destination-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(59, 130, 246, 0.1);
border: 2px solid #3b82f6;
pointer-events: none;
z-index: 5;
}
</style>
</head>
<body class="antialiased bg-gray-900">
<nav class="bg-gray-800 border-b border-gray-700 sticky top-0 z-30">
<div class="container mx-auto px-4">
<div class="flex justify-between items-center h-16">
<div
class="flex-shrink-0 flex items-center cursor-pointer"
id="home-logo"
>
<img
src="../../public/images/favicon.svg"
alt="Bento PDF Logo"
class="h-8 w-8"
/>
<span class="text-white font-bold text-xl ml-2">
<a href="index.html">BentoPDF</a>
</span>
</div>
<!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8 text-white">
<a href="index.html" class="nav-link">Home</a>
<a href="./about.html" class="nav-link">About</a>
<a href="./contact.html" class="nav-link">Contact</a>
<a href="index.html#tools-header" class="nav-link">All Tools</a>
</div>
<!-- Mobile Hamburger Button -->
<div class="md:hidden flex items-center">
<button
id="mobile-menu-button"
type="button"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500 transition-colors"
aria-controls="mobile-menu"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<!-- Hamburger Icon -->
<svg
id="menu-icon"
class="block h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
<!-- Close Icon -->
<svg
id="close-icon"
class="hidden h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile Menu Dropdown -->
<div
id="mobile-menu"
class="hidden md:hidden bg-gray-800 border-t border-gray-700"
>
<div class="px-2 pt-2 pb-3 space-y-1 text-center">
<a href="index.html" class="mobile-nav-link">Home</a>
<a href="./about.html" class="mobile-nav-link">About</a>
<a href="./contact.html" class="mobile-nav-link">Contact</a>
<a href="index.html#tools-header" class="mobile-nav-link"
>All Tools</a
>
</div>
</div>
</nav>
<div
id="uploader"
class="min-h-screen flex items-center justify-center p-4 bg-gray-900"
>
<div
class="bg-gray-900 rounded-xl shadow-xl p-8 max-w-md w-full text-gray-200"
>
<p class="text-gray-400 mb-6">
Upload a PDF to begin editing bookmarks
</p>
<!-- Drop Zone for Main PDF Upload -->
<div
id="drop-zone"
class="relative flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-800 hover:bg-gray-700 transition-colors duration-300"
>
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<i
data-lucide="upload-cloud"
class="w-10 h-10 mb-3 text-gray-400"
></i>
<p class="mb-2 text-sm text-gray-300">
<span class="font-semibold">Click to select a file</span> or drag
and drop
</p>
<p class="text-xs text-gray-500">A single PDF file</p>
<p class="text-xs text-gray-500">
Your files never leave your device.
</p>
</div>
<input
type="file"
id="file-input"
accept=".pdf"
class="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
/>
</div>
<!-- Auto Extract Checkbox -->
<div class="mt-4">
<label class="flex items-center gap-2 text-sm text-gray-300">
<input
type="checkbox"
id="auto-extract-checkbox"
class="w-4 h-4 accent-blue-500"
/>
<span>Auto-extract existing bookmarks</span>
</label>
</div>
<!-- Import Bookmarks Section -->
<div class="mt-8 pt-6 border-t border-gray-700">
<h3 class="text-sm font-semibold text-gray-300 mb-4">
Or Import Bookmarks
</h3>
<!-- CSV Upload -->
<label class="block mb-4">
<span class="text-xs text-gray-500"
>CSV format: title,page,level</span
>
<div
class="relative flex flex-col items-center justify-center w-full h-32 mt-2 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-800 hover:bg-gray-700 transition-colors duration-300"
>
<div class="flex flex-col items-center justify-center pt-4 pb-4">
<i
data-lucide="sheet"
class="w-8 h-8 mb-2 text-gray-400"
></i>
<p class="text-xs text-gray-400">
<span class="font-semibold">Click to upload CSV</span> or drag
here
</p>
</div>
<input
type="file"
id="csv-input"
accept=".csv"
class="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
/>
</div>
</label>
<!-- JSON Upload -->
<label class="block">
<span class="text-xs text-gray-500">JSON format</span>
<div
class="relative flex flex-col items-center justify-center w-full h-32 mt-2 border-2 border-dashed border-gray-600 rounded-xl cursor-pointer bg-gray-800 hover:bg-gray-700 transition-colors duration-300"
>
<div class="flex flex-col items-center justify-center pt-4 pb-4">
<i data-lucide="braces" class="w-8 h-8 mb-2 text-gray-400"></i>
<p class="text-xs text-gray-400">
<span class="font-semibold">Click to upload JSON</span> or
drag here
</p>
</div>
<input
type="file"
id="json-input"
accept=".json"
class="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
/>
</div>
</label>
</div>
</div>
</div>
<div id="app" class="hidden bg-gray-900 min-h-screen">
<header class="bg-gray-800 border-b border-gray-700 shadow-sm sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 py-3">
<div class="flex items-center justify-between flex-wrap gap-2">
<h1 class="text-xl font-bold text-white" id="filename-display">
PDF Bookmark Editor
</h1>
<div class="flex gap-2 flex-wrap">
<button
id="undo-btn"
class="px-3 py-2 text-sm bg-gray-700 hover:bg-gray-600 text-white rounded disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1"
disabled
>
<i data-lucide="undo-2" class="w-4 h-4"></i> Undo
</button>
<button
id="redo-btn"
class="px-3 py-2 text-sm bg-gray-700 hover:bg-gray-600 text-white rounded disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1"
disabled
>
<i data-lucide="redo-2" class="w-4 h-4"></i> Redo
</button>
<button
id="delete-all-btn"
class="px-3 py-2 text-sm bg-red-600 hover:bg-red-700 text-white rounded flex items-center gap-1"
>
<i data-lucide="trash-2" class="w-4 h-4"></i> Delete All
</button>
<button
id="reset-btn"
class="px-3 py-2 text-sm bg-orange-600 hover:bg-orange-700 text-white rounded flex items-center gap-1"
>
<i data-lucide="rotate-ccw" class="w-4 h-4"></i> Reset
</button>
</div>
</div>
</div>
</header>
<div class="lg:hidden bg-gray-800 border-b border-gray-700">
<div class="flex">
<button
id="show-viewer-btn"
class="flex-1 py-3 text-sm font-medium bg-indigo-600 text-white"
>
PDF Viewer
</button>
<button
id="show-bookmarks-btn"
class="flex-1 py-3 text-sm font-medium text-gray-300 hover:text-white"
>
Bookmarks
</button>
</div>
</div>
<div class="max-w-7xl mx-auto p-4">
<div class="flex flex-col lg:flex-row gap-4">
<div id="viewer-section" class="w-full lg:w-[50%] flex-shrink-0">
<div class="bg-white rounded-lg shadow-lg p-4">
<div
class="flex flex-wrap items-center justify-between gap-2 mb-4"
>
<div class="flex items-center gap-2">
<button
id="prev-page"
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded text-sm"
>
←
</button>
<span
id="page-indicator"
class="text-sm font-medium text-gray-700 whitespace-nowrap"
>Page 1 / 1</span
>
<button
id="next-page"
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded text-sm"
>
→
</button>
</div>
<div class="flex items-center gap-2">
<span class="text-sm text-gray-600">Go to:</span>
<input
type="number"
id="goto-page"
min="1"
class="w-16 px-2 py-1 border border-gray-300 rounded text-sm"
/>
<button
id="goto-btn"
class="px-3 py-1 bg-blue-500 hover:bg-blue-600 text-white rounded text-sm"
>
Go
</button>
</div>
<div class="flex items-center gap-2">
<button
id="zoom-out-btn"
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded text-sm"
title="Zoom Out"
>
−
</button>
<span
id="zoom-indicator"
class="text-sm font-medium text-gray-700 whitespace-nowrap min-w-[60px] text-center"
>100%</span
>
<button
id="zoom-in-btn"
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded text-sm"
title="Zoom In"
>
+
</button>
<button
id="zoom-fit-btn"
class="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded text-sm"
title="Fit to Width"
>
Fit
</button>
</div>
</div>
<div
id="picking-mode-banner"
class="hidden mb-4 p-3 bg-blue-100 border-2 border-blue-400 rounded-lg"
>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<i
data-lucide="crosshair"
class="w-5 h-5 text-blue-600"
></i>
<span class="text-sm font-medium text-blue-800"
>Click on the PDF to set bookmark destination</span
>
</div>
<button
id="cancel-picking-btn"
class="px-3 py-1 bg-red-500 hover:bg-red-600 text-white rounded text-xs"
>
Cancel
</button>
</div>
</div>
<div
class="overflow-auto border border-gray-200 rounded"
id="canvas-container"
>
<div class="pdf-canvas-wrapper" id="pdf-canvas-wrapper">
<canvas
id="pdf-canvas"
class="max-w-full h-auto mx-auto"
></canvas>
</div>
</div>
</div>
</div>
<div
id="bookmarks-section"
class="hidden lg:block w-full lg:w-[50%] flex-shrink-0"
>
<div class="bg-white rounded-lg shadow-lg p-4">
<h3 class="text-lg font-bold text-gray-800 mb-4">Bookmarks</h3>
<div class="mb-4">
<div class="relative">
<i
data-lucide="search"
class="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400"
></i>
<input
type="text"
id="search-bookmarks"
placeholder="Search bookmarks..."
class="w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg text-sm"
/>
</div>
</div>
<div class="mb-4 p-3 bg-gray-50 rounded-lg">
<input
type="text"
id="bookmark-title"
placeholder="Bookmark title..."
class="w-full px-3 py-2 border border-gray-300 rounded-lg mb-2 text-sm"
/>
<button
id="add-top-level-btn"
class="w-full px-3 py-2 bg-gradient-to-b from-blue-500 to-blue-600 hover:shadow-xl text-white rounded text-sm font-medium flex items-center justify-center gap-2 focus:ring-2 focus:ring-blue-400 transition duration-200"
>
<i data-lucide="plus" class="w-4 h-4"></i>
<span
>Add to Page <span id="current-page-display">1</span></span
>
</button>
</div>
<div class="mb-4">
<label
class="flex items-center gap-2 text-sm font-medium text-gray-700 mb-2"
>
<input
type="checkbox"
id="batch-mode-checkbox"
class="w-4 h-4"
/>
<span
>Batch Mode (<span id="selected-count">0</span>
selected)</span
>
</label>
<div
id="batch-operations"
class="hidden p-3 bg-blue-50 rounded-lg border border-blue-200"
>
<div class="text-xs font-semibold text-gray-700 mb-2">
Batch Operations
</div>
<div class="flex flex-col gap-2">
<div class="flex gap-2">
<button
id="select-all-btn"
class="flex-1 px-3 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded text-xs flex items-center justify-center gap-1"
>
<i data-lucide="check-square" class="w-3 h-3"></i>
Select All
</button>
<button
id="deselect-all-btn"
class="flex-1 px-3 py-2 bg-gray-500 hover:bg-gray-600 text-white rounded text-xs flex items-center justify-center gap-1"
>
<i data-lucide="square" class="w-3 h-3"></i> Deselect
All
</button>
</div>
<select
id="batch-color-select"
class="text-xs p-2 border rounded"
>
<option value="">Set Color...</option>
<option value="null">None</option>
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
<option value="yellow">Yellow</option>
<option value="purple">Purple</option>
</select>
<select
id="batch-style-select"
class="text-xs p-2 border rounded"
>
<option value="">Set Style...</option>
<option value="null">Normal</option>
<option value="bold">Bold</option>
<option value="italic">Italic</option>
<option value="bold-italic">Bold & Italic</option>
</select>
<button
id="batch-delete-btn"
class="px-3 py-2 bg-red-500 hover:bg-red-600 text-white rounded text-xs flex items-center justify-center gap-1"
>
<i data-lucide="trash-2" class="w-3 h-3"></i> Delete
Selected
</button>
</div>
</div>
</div>
<div class="flex gap-2 mb-4">
<button
id="expand-all-btn"
class="flex-1 px-3 py-2 bg-gradient-to-b from-gray-100 to-gray-200 hover:shadow-md text-gray-700 rounded text-sm font-medium flex items-center justify-center gap-1 focus:ring-2 focus:ring-gray-300 transition duration-200"
>
<i data-lucide="chevrons-down" class="w-4 h-4"></i> Expand All
</button>
<button
id="collapse-all-btn"
class="flex-1 px-3 py-2 bg-gradient-to-b from-gray-100 to-gray-200 hover:shadow-md text-gray-700 rounded text-sm font-medium flex items-center justify-center gap-1 focus:ring-2 focus:ring-gray-300 transition duration-200"
>
<i data-lucide="chevrons-up" class="w-4 h-4"></i> Collapse All
</button>
</div>
<div class="grid grid-cols-2 gap-2 mb-4">
<div class="relative">
<button
id="import-dropdown-btn"
class="w-full px-3 py-2 bg-gradient-to-b from-gray-100 to-gray-200 hover:shadow-md text-gray-700 rounded text-sm font-medium flex items-center justify-center gap-1 focus:ring-2 focus:ring-gray-300 transition duration-200"
>
<i data-lucide="upload" class="w-4 h-4"></i> Import
<i data-lucide="chevron-down" class="w-3 h-3"></i>
</button>
<div
id="import-dropdown"
class="hidden absolute top-full left-0 mt-1 w-full bg-white border border-gray-200 rounded-lg shadow-lg z-10"
>
<button
id="import-csv-btn"
class="w-full px-3 py-2 text-sm text-left flex items-center gap-2 rounded-t-lg bg-gradient-to-b from-gray-50 to-gray-100 hover:shadow-sm hover:from-gray-100 hover:to-gray-200 transition duration-150"
>
<i
data-lucide="file-text"
class="w-4 h-4 text-gray-600"
></i>
Import CSV
</button>
<button
id="import-json-btn"
class="w-full px-3 py-2 text-sm text-left flex items-center gap-2 rounded-b-lg bg-gradient-to-b from-gray-50 to-gray-100 hover:shadow-sm hover:from-gray-100 hover:to-gray-200 transition duration-150"
>
<i data-lucide="braces" class="w-4 h-4 text-gray-600"></i>
Import JSON
</button>
</div>
</div>
<div class="relative">
<button
id="export-dropdown-btn"
class="w-full px-3 py-2 bg-gradient-to-b from-gray-100 to-gray-200 hover:shadow-md text-gray-700 rounded text-sm font-medium flex items-center justify-center gap-1 focus:ring-2 focus:ring-gray-300 transition duration-200"
>
<i data-lucide="download" class="w-4 h-4"></i> Export
<i data-lucide="chevron-down" class="w-3 h-3"></i>
</button>
<div
id="export-dropdown"
class="hidden absolute top-full left-0 mt-1 w-full bg-white border border-gray-200 rounded-lg shadow-lg z-10"
>
<button
id="export-csv-btn"
class="w-full px-3 py-2 text-sm text-left hover:bg-gray-50 flex items-center gap-2 rounded-t-lg"
>
<i data-lucide="file-text" class="w-4 h-4"></i> Export CSV
</button>
<button
id="export-json-btn"
class="w-full px-3 py-2 text-sm text-left hover:bg-gray-50 flex items-center gap-2 rounded-b-lg"
>
<i data-lucide="braces" class="w-4 h-4"></i> Export JSON
</button>
</div>
</div>
</div>
<div
class="mb-4 max-h-96 overflow-y-auto border border-gray-200 rounded-lg p-2"
>
<ul id="bookmark-tree-list" class="space-y-2"></ul>
<div
id="no-bookmarks"
class="text-center py-8 text-gray-400 text-sm"
>
No bookmarks yet. Add one above!
</div>
</div>
<div class="space-y-2">
<button
id="download-btn"
class="w-full px-4 py-2 bg-gradient-to-b from-blue-600 to-blue-700 hover:shadow-xl text-white rounded font-medium flex items-center justify-center gap-2 focus:ring-2 focus:ring-blue-400 transition duration-200"
>
Save PDF with Bookmarks
</button>
<button
id="extract-existing-btn"
class="w-full px-4 py-2 bg-gradient-to-b from-green-500 to-green-600 hover:shadow-xl text-white rounded font-medium flex items-center justify-center gap-2 focus:ring-2 focus:ring-green-400 transition duration-200"
>
Extract Existing Bookmarks
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<input type="file" id="csv-import-hidden" accept=".csv" class="hidden" />
<input type="file" id="json-import-hidden" accept=".json" class="hidden" />
<div id="modal-container"></div>
<footer class="mt-16 border-t-2 border-gray-700 py-8 bg-[#111827]">
<div class="container mx-auto px-4">
<div
class="grid grid-cols-1 md:grid-cols-4 gap-8 text-center md:text-left"
>
<div class="mb-8 md:mb-0">
<div class="flex items-center justify-center md:justify-start mb-4">
<img
src="../../public/images/favicon.svg"
alt="Bento PDF Logo"
class="h-10 w-10 mr-3"
/>
<span class="text-xl font-bold text-white">BentoPDF</span>
</div>
<p class="text-gray-400 text-sm">
&copy; 2025 BentoPDF. All rights reserved.
</p>
</div>
<div>
<h3 class="font-bold text-white mb-4">Company</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="./about.html" class="hover:text-indigo-400"
>About Us</a
>
</li>
<li>
<a href="./faq.html" class="hover:text-indigo-400">FAQ</a>
</li>
<li>
<a href="./contact.html" class="hover:text-indigo-400"
>Contact Us</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Legal</h3>
<ul class="space-y-2 text-gray-400">
<li>
<a href="./terms.html" class="hover:text-indigo-400"
>Terms and Conditions</a
>
</li>
<li>
<a href="./privacy.html" class="hover:text-indigo-400"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div>
<h3 class="font-bold text-white mb-4">Follow Us</h3>
<div class="flex justify-center md:justify-start space-x-4">
<a
href="https://github.com/alam00000/bentopdf"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="GitHub"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clip-rule="evenodd"
/>
</svg>
</a>
<a
href="https://discord.gg/q42xWQmJ"
target="_blank"
rel="noopener noreferrer"
class="text-gray-400 hover:text-indigo-400"
title="Discord"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z"
/>
</svg>
</a>
<a
href="https://www.instagram.com/thebentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="Instagram"
>
<i data-lucide="instagram"></i>
</a>
<a
href="https://www.linkedin.com/company/bentopdf/"
class="text-gray-400 hover:text-indigo-400"
title="LinkedIn"
>
<i data-lucide="linkedin"></i>
</a>
<a
href="https://x.com/BentoPDF"
class="text-gray-400 hover:text-indigo-400"
title="X (Twitter)"
>
<svg
class="w-6 h-6"
fill="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
</div>
</div>
</div>
</div>
</footer>
<script type="module" src="../js/utils/lucide-init.ts"></script>
<script type="module" src="../js/logic/bookmark-pdf.ts"></script>
<script type="module" src="../js/mobileMenu.ts"></script>
</body>
</html>
+2 -6
View File
@@ -4,9 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Terms and Conditions - Bentopdf</title>
<script src="https://unpkg.com/lucide@latest"></script>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="icon" type="image/png" href="images/favicon.svg" />
<link href="/src/css/styles.css" rel="stylesheet" />
</head>
@@ -307,9 +304,8 @@
</div>
</div>
</footer>
<script>
lucide.createIcons();
</script>
<script type="module" src="src/js/utils/lucide-init.ts"></script>
<script type="module" src="src/js/mobileMenu.ts"></script>
</body>
</html>
+1
View File
@@ -36,6 +36,7 @@ export default defineConfig(({ mode }) => ({
faq: resolve(__dirname, 'faq.html'),
privacy: resolve(__dirname, 'privacy.html'),
terms: resolve(__dirname, 'terms.html'),
bookmark: resolve(__dirname, 'src/pages/bookmark.html'),
},
},
},