-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtools.js
More file actions
151 lines (141 loc) · 42.2 KB
/
Copy pathtools.js
File metadata and controls
151 lines (141 loc) · 42.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
'use strict';
const TOOLS = {
// ── PDF ──────────────────────────────────────────────
'compress-pdf':{cat:'pdf',ic:'compress',label:'Compress PDF',desc:'Reduce PDF file size while preserving quality.',accept:'.pdf',multi:false,tips:['Works best on PDFs containing images','Smaller size = faster email & upload','Original file is never modified'],opts:[{id:'q',label:'Compression Level',type:'select',choices:['Low — best quality','Medium — balanced','High — smallest file'],def:'Medium — balanced'}]},
'merge-pdf':{cat:'pdf',ic:'merge',label:'Merge PDFs',desc:'Combine multiple PDF files into one document.',accept:'.pdf',multi:true,tips:['Upload files in the order you want them merged','Works with any number of PDFs','Bookmarks and links are preserved'],opts:[]},
'split-pdf':{cat:'pdf',ic:'split',label:'Split PDF',desc:'Extract specific pages or split into separate files.',accept:'.pdf',multi:false,tips:['Use "1,3,5" for specific pages','Use "2-8" for a page range','"all" extracts every page separately'],opts:[{id:'pages',label:'Pages to Extract',sub:'e.g. 1,3,5-8 or "all" for every page',type:'text',def:'all'}]},
'rotate-pdf':{cat:'pdf',ic:'rotate',label:'Rotate PDF',desc:'Rotate all pages to correct orientation.',accept:'.pdf',multi:false,tips:['Rotation applies to all pages uniformly','Use 180° to flip upside-down scans'],opts:[{id:'angle',label:'Rotation Angle',type:'select',choices:['90° Clockwise','180°','90° Counter-Clockwise'],def:'90° Clockwise'}]},
'pdf-to-jpg':{cat:'pdf',ic:'img',label:'PDF → JPG',desc:'Convert each PDF page to a high-quality JPG image.',accept:'.pdf',multi:false,tips:['2× scale gives crisp results for most uses','All pages are packed into a ZIP file','Higher scale = larger file size'],opts:[{id:'scale',label:'Resolution',type:'range',min:1,max:3,step:.5,def:2,unit:'×'},{id:'quality',label:'JPEG Quality',type:'range',min:60,max:100,step:1,def:92,unit:'%'}]},
'jpg-to-pdf':{cat:'pdf',ic:'pdf',label:'JPG → PDF',desc:'Combine one or more images into a single PDF.',accept:'image/*',multi:true,tips:['Upload multiple images to combine them','Each image becomes one page','Use "Fit to Image" to preserve original dimensions'],opts:[{id:'pagesize',label:'Page Size',type:'select',choices:['A4 Portrait','A4 Landscape','Letter Portrait','Fit to Image'],def:'A4 Portrait'},{id:'margin',label:'Margin',type:'select',choices:['None','Small (5mm)','Medium (15mm)','Large (25mm)'],def:'Medium (15mm)'}]},
'unlock-pdf':{cat:'pdf',ic:'unlock',label:'Unlock PDF',desc:'Remove password protection from a PDF file.',accept:'.pdf',multi:false,tips:['You must know the current password to unlock','Works only on user-password protected PDFs'],opts:[{id:'pass',label:'Current Password',type:'password',def:''}]},
'protect-pdf':{cat:'pdf',ic:'lock',label:'Protect PDF',desc:'Add a password to prevent unauthorized access.',accept:'.pdf',multi:false,tips:['Use a strong password with mixed characters','Restrict printing and copying for extra security'],opts:[{id:'p1',label:'New Password',type:'password',def:''},{id:'p2',label:'Confirm Password',type:'password',def:''},{id:'noPrint',label:'Restrict Printing',type:'toggle',def:false},{id:'noCopy',label:'Restrict Copying',type:'toggle',def:false}]},
'add-page-numbers':{cat:'pdf',ic:'number',label:'Page Numbers',desc:'Add page numbers to every page of a PDF.',accept:'.pdf',multi:false,tips:['Use prefix like "Page" to get "Page 1"','Skip first page option for cover pages','{page} inserts page number in text fields'],opts:[{id:'pos',label:'Position',type:'select',choices:['Bottom Centre','Bottom Right','Bottom Left','Top Centre','Top Right'],def:'Bottom Centre'},{id:'start',label:'Start From',type:'number',def:'1'},{id:'prefix',label:'Prefix Text',sub:'Optional — e.g. "Page" gives "Page 1"',type:'text',def:''},{id:'skipFirst',label:'Skip First Page',type:'toggle',def:false}]},
'stamp-pdf':{cat:'pdf',ic:'stamp',label:'Stamp PDF',desc:'Add a diagonal text watermark to every page.',accept:'.pdf',multi:false,tips:['Common stamps: CONFIDENTIAL, DRAFT, APPROVED','Lower opacity makes text less intrusive','Red and grey are most professional'],opts:[{id:'text',label:'Stamp Text',type:'text',def:'CONFIDENTIAL'},{id:'opacity',label:'Opacity',type:'range',min:5,max:60,step:5,def:15,unit:'%'},{id:'color',label:'Colour',type:'select',choices:['Red','Blue','Grey','Black','Green'],def:'Red'},{id:'fontSize',label:'Font Size',type:'select',choices:['Small','Medium','Large'],def:'Medium'}]},
'pdf-info':{cat:'pdf',ic:'info',label:'PDF Inspector',desc:'View detailed metadata, page count, and file info.',accept:'.pdf',multi:false,tips:['Shows author, creator, creation date','Exports a full text report','Useful for audit and compliance checks'],opts:[]},
'delete-pdf-pages':{cat:'pdf',ic:'del',label:'Delete Pages',desc:'Remove specific pages from a PDF document.',accept:'.pdf',multi:false,tips:['Enter pages to DELETE, not to keep','e.g. "1,3" removes pages 1 and 3','Ranges work too: "2-5" removes pages 2–5'],opts:[{id:'pages',label:'Pages to Delete',sub:'e.g. 1,3 or 2-5 (these pages will be removed)',type:'text',def:''}]},
'duplicate-pdf-pages':{cat:'pdf',ic:'copy',label:'Duplicate Pages',desc:'Copy a specific page multiple times within a PDF.',accept:'.pdf',multi:false,tips:['Duplicate is inserted after the original','Useful for creating form templates'],opts:[{id:'pagenum',label:'Page to Duplicate',type:'number',def:'1'},{id:'copies',label:'Number of Copies',type:'number',def:'2'}]},
'reorder-pdf':{cat:'pdf',ic:'reorder',label:'Reorder Pages',desc:'Rearrange PDF pages into any custom order.',accept:'.pdf',multi:false,tips:['Enter ALL page numbers in your desired order','e.g. "3,1,2" moves page 3 to the front'],opts:[{id:'order',label:'New Page Order',sub:'Enter ALL pages in desired order e.g. 3,1,2,4',type:'text',def:''}]},
'pdf-header-footer':{cat:'pdf',ic:'header',label:'Header & Footer',desc:'Add custom text to the top and bottom of every page.',accept:'.pdf',multi:false,tips:['Use {page} for page number','Use {date} for today\'s date','Leave blank to skip header or footer'],opts:[{id:'header',label:'Header Text',sub:'Use {page} and {date} as placeholders',type:'text',def:''},{id:'footer',label:'Footer Text',sub:'Leave blank to skip',type:'text',def:'Page {page}'},{id:'align',label:'Alignment',type:'select',choices:['Left','Centre','Right'],def:'Centre'}]},
'flatten-pdf':{cat:'pdf',ic:'flatten',label:'Flatten PDF',desc:'Flatten form fields and annotations into static content.',accept:'.pdf',multi:false,tips:['Flattened PDFs cannot be further edited','Good for finalising documents before sharing','Reduces file size in some cases'],opts:[]},
'pdf-thumbnail':{cat:'pdf',ic:'thumb',label:'PDF Thumbnails',desc:'Generate JPG thumbnail images for all pages.',accept:'.pdf',multi:false,tips:['All thumbnails are packed into a ZIP','Useful for document preview systems','Medium size (1×) is best for most uses'],opts:[{id:'scale',label:'Thumbnail Size',type:'select',choices:['Small (0.5×)','Medium (1×)','Large (1.5×)'],def:'Medium (1×)'}]},
'pdf-to-csv':{cat:'pdf',ic:'table',label:'PDF → CSV',desc:'Extract table data from a PDF into a CSV spreadsheet.',accept:'.pdf',multi:false,tips:['Works best on simple text-based tables','Complex or scanned PDFs may not extract correctly','Always verify the output after extraction'],opts:[{id:'separator',label:'CSV Separator',type:'select',choices:['Comma (,)','Semicolon (;)','Tab'],def:'Comma (,)'}]},
'pdf-bookmarks':{cat:'pdf',ic:'bookmark',label:'PDF Bookmarks',desc:'Add a clickable table of contents to a PDF.',accept:'.pdf',multi:false,tips:['Format: Title: PageNumber on each line','Bookmarks appear in PDF reader sidebar'],opts:[{id:'bookmarks',label:'Bookmarks',sub:'Format: Title: PageNumber — one per line',type:'__ta__',def:'Introduction: 1\nChapter 1: 3\nConclusion: 10'}]},
'pdf-to-word':{cat:'pdf',ic:'text',label:'PDF → Word',desc:'Convert PDF to DOCX. Visual mode preserves 100% layout as a crisp image. Editable mode reconstructs real movable text boxes over the page — select, edit and reposition every element.',accept:'.pdf',multi:false,tips:['Visual mode: every page rendered as image — pixel-perfect but not editable','Editable mode: background image + floating text boxes — real selectable, movable text over the original layout','Higher quality = sharper background image (both modes)'],opts:[{id:'convMode',label:'Conversion Mode',type:'select',choices:['Visual (Exact Layout)','Text (Editable)'],def:'Visual (Exact Layout)'},{id:'quality',label:'Render Quality',sub:'Higher = sharper but larger file',type:'select',choices:['1 — Fast / Small','2 — Balanced (Recommended)','3 — High Quality'],def:'2 — Balanced (Recommended)'}]},
'word-to-pdf':{cat:'pdf',ic:'pdf',label:'Word → PDF',desc:'Convert .docx or .txt files into a clean, properly formatted PDF.',accept:'.doc,.docx,.txt',multi:false,tips:['Reads actual DOCX text content — not just raw bytes','Auto-detects headings for better formatting','Long documents are paginated automatically'],opts:[]},
'ocr-pdf':{cat:'pdf',ic:'search',label:'PDF Text Extractor',desc:'Extract all selectable text from a PDF into a plain text or Markdown file.',accept:'.pdf',multi:false,tips:['Works on text-based PDFs — scanned image PDFs cannot be extracted','Leave page range empty to extract all pages','Use Markdown output to preserve heading structure'],opts:[{id:'ocrFmt',label:'Output Format',type:'select',choices:['Plain Text','Markdown'],def:'Plain Text'},{id:'ocrPages',label:'Page Range',sub:'e.g., 1-3, 5, 7 — leave empty for all pages',type:'text',def:''}]},
// ── IMAGE ────────────────────────────────────────────
'compress-image':{cat:'img',ic:'compress',label:'Compress Image',desc:'Reduce image file size with quality control and before/after preview.',accept:'image/jpeg,image/png,image/webp',multi:true,tips:['70–85% quality is ideal for web use','Batch compress multiple images at once','Before/after comparison shown for single files'],opts:[{id:'quality',label:'Quality',type:'range',min:10,max:95,step:1,def:78,unit:'%'},{id:'maxw',label:'Max Width (px)',sub:'Leave blank to keep original width',type:'number',def:''}]},
'resize-image':{cat:'img',ic:'resize',label:'Resize Image',desc:'Resize images by pixels or percentage with live preview.',accept:'image/*',multi:false,tips:['Live preview updates as you type','Lock aspect ratio to prevent distortion','Social media presets included'],opts:'__resize__'},
'crop-image':{cat:'img',ic:'crop',label:'Crop Image',desc:'Visually select and crop any area of your image.',accept:'image/*',multi:false,tips:['Click and drag on the image to select area','Use aspect lock buttons for exact ratios','Fine-tune coordinates with input fields'],opts:[]},
'convert-to-jpg':{cat:'img',ic:'convert',label:'Convert → JPG',desc:'Convert PNG, WEBP, GIF or BMP images to JPEG.',accept:'image/png,image/webp,image/gif,image/bmp',multi:true,tips:['White background fills transparent areas by default','Batch convert multiple files at once'],opts:[{id:'quality',label:'JPEG Quality',type:'range',min:50,max:100,step:1,def:92,unit:'%'},{id:'bgcolor',label:'Background Fill',type:'select',choices:['White','Black','Light Grey'],def:'White'}]},
'convert-from-jpg':{cat:'img',ic:'convert',label:'JPG → PNG/WEBP',desc:'Convert JPEG images to PNG or WEBP format.',accept:'image/jpeg,image/jpg',multi:true,tips:['PNG is lossless — perfect for graphics','WEBP is ~30% smaller — ideal for web'],opts:[{id:'fmt',label:'Output Format',type:'select',choices:['PNG — Lossless','WEBP — Smaller'],def:'PNG — Lossless'}]},
'grayscale':{cat:'img',ic:'bw',label:'Grayscale',desc:'Convert colour images to black and white.',accept:'image/*',multi:true,tips:['Enhance Contrast improves definition','Batch process multiple images at once'],opts:[{id:'contrast',label:'Enhance Contrast',type:'toggle',def:false}]},
'watermark-image':{cat:'img',ic:'water',label:'Watermark',desc:'Add a custom text watermark to protect your images.',accept:'image/*',multi:false,tips:['Diagonal placement is harder to remove','Lower opacity creates a subtle watermark','Tiled covers the entire image'],opts:[{id:'text',label:'Watermark Text',type:'text',def:'© Your Company'},{id:'opacity',label:'Opacity',type:'range',min:10,max:90,step:5,def:30,unit:'%'},{id:'pos',label:'Position',type:'select',choices:['Centre (diagonal)','Bottom Right','Bottom Left','Top Right','Top Left','Tiled'],def:'Centre (diagonal)'},{id:'color',label:'Text Colour',type:'select',choices:['White','Black','Red','Blue','Grey'],def:'White'}]},
'flip-image':{cat:'img',ic:'flip',label:'Flip & Mirror',desc:'Flip images horizontally, vertically or both.',accept:'image/*',multi:true,tips:['Horizontal flip = mirror effect','Batch process multiple files at once'],opts:[{id:'dir',label:'Direction',type:'select',choices:['Horizontal (Mirror)','Vertical (Flip)','Both'],def:'Horizontal (Mirror)'}]},
'image-borders':{cat:'img',ic:'border',label:'Add Border',desc:'Add a coloured frame or border around your image.',accept:'image/*',multi:false,tips:['White borders give a photo-print look','Use corner radius for rounded frames'],opts:[{id:'bsize',label:'Border Width (px)',type:'number',def:'20'},{id:'bcolor',label:'Border Colour (hex)',type:'text',def:'#ffffff'},{id:'bradius',label:'Corner Radius (px)',type:'number',def:'0'}]},
'round-corners':{cat:'img',ic:'corner',label:'Round Corners',desc:'Apply rounded corners and save as transparent PNG.',accept:'image/*',multi:false,tips:['Output is always PNG to preserve transparency','Try radius 40–80 for a natural look'],opts:[{id:'radius',label:'Corner Radius (px)',type:'number',def:'40'},{id:'bg',label:'Background',type:'select',choices:['Transparent (PNG)','White','Black'],def:'Transparent (PNG)'}]},
'blur-image':{cat:'img',ic:'blur',label:'Blur Image',desc:'Apply Gaussian blur for a soft focus or privacy effect.',accept:'image/*',multi:false,tips:['Higher radius = more blurred','Use blur to anonymise faces or backgrounds'],opts:[{id:'radius',label:'Blur Radius',type:'range',min:1,max:30,step:1,def:5,unit:'px'}]},
'exif-tool':{cat:'img',ic:'exif',label:'EXIF Viewer',desc:'View or strip hidden metadata from JPEG photos.',accept:'image/jpeg,image/jpg',multi:false,tips:['EXIF data includes GPS, camera model, dates','Strip before sharing photos to protect privacy'],opts:[{id:'mode',label:'Mode',type:'select',choices:['View EXIF data','Strip all EXIF'],def:'View EXIF data'}]},
'colour-palette':{cat:'img',ic:'palette',label:'Colour Palette',desc:'Extract the dominant colours from any image.',accept:'image/*',multi:false,tips:['Click any colour swatch to copy its hex code','Uses K-means clustering algorithm','Exports as CSS variables and hex list'],opts:[{id:'count',label:'Number of Colours',type:'range',min:2,max:12,step:1,def:6,unit:''}]},
'image-collage':{cat:'img',ic:'collage',label:'Collage Maker',desc:'Combine multiple images into a single collage.',accept:'image/*',multi:true,tips:['Upload 2–9 images for best results','Auto Grid picks the best layout automatically'],opts:[{id:'layout',label:'Layout',type:'select',choices:['Auto Grid','2 Columns','3 Columns','Horizontal Strip','Vertical Strip'],def:'Auto Grid'},{id:'gap',label:'Gap Between Images',type:'range',min:0,max:24,step:2,def:4,unit:'px'},{id:'bg',label:'Background Colour (hex)',type:'text',def:'#ffffff'},{id:'size',label:'Output Width (px)',type:'number',def:'1200'}]},
'image-info':{cat:'img',ic:'info',label:'Image Inspector',desc:'Instantly view dimensions, format, file size and metadata.',accept:'image/*',multi:false,tips:['Results shown instantly on upload','Exports a full info report as text'],opts:[]},
'sprite-sheet':{cat:'img',ic:'sprite',label:'Sprite Sheet',desc:'Pack multiple images into a sprite sheet with CSS map.',accept:'image/*',multi:true,tips:['Reduces HTTP requests in web development','CSS file included with background-position values'],opts:[{id:'cellW',label:'Cell Width (px)',type:'number',def:'64'},{id:'cellH',label:'Cell Height (px)',type:'number',def:'64'},{id:'cols',label:'Columns',type:'number',def:'4'}]},
// ── TEXT & OFFICE ────────────────────────────────────
'text-case':{cat:'text',ic:'text',label:'Text Case',desc:'Convert text between UPPERCASE, camelCase, snake_case and more.',accept:null,multi:false,tips:['No file needed — paste text directly','Supports 9 case formats including camelCase and CONSTANT_CASE'],opts:[{id:'inputText',label:'Input Text',type:'__ta__',def:'Paste your text here…'},{id:'case',label:'Output Case',type:'select',choices:['UPPERCASE','lowercase','Title Case','Sentence case','camelCase','PascalCase','snake_case','kebab-case','CONSTANT_CASE'],def:'Title Case'}]},
'lorem-ipsum':{cat:'text',ic:'lorem',label:'Lorem Ipsum',desc:'Generate placeholder text for designs and mockups.',accept:null,multi:false,tips:['Paragraphs, sentences or words mode','Start with classic "Lorem ipsum" opening'],opts:[{id:'type',label:'Type',type:'select',choices:['Paragraphs','Sentences','Words'],def:'Paragraphs'},{id:'count',label:'Amount',type:'number',def:'3'},{id:'startLorem',label:'Classic Opening',sub:'Start with "Lorem ipsum dolor sit amet…"',type:'toggle',def:true}]},
'find-replace':{cat:'text',ic:'search',label:'Find & Replace',desc:'Find and replace text across any text-based file.',accept:'.txt,.md,.csv,.json,.xml,.html',multi:false,tips:['Supports regex for advanced pattern matching','Case insensitive mode available'],opts:[{id:'find',label:'Find',type:'text',def:''},{id:'replace',label:'Replace With',type:'text',def:''},{id:'regex',label:'Use Regex',type:'toggle',def:false},{id:'ci',label:'Case Insensitive',type:'toggle',def:false}]},
'json-formatter':{cat:'text',ic:'json',label:'JSON Formatter',desc:'Beautify, minify and validate JSON files.',accept:'.json,.txt',multi:false,tips:['Catches syntax errors with clear messages','Minify removes all whitespace for smaller files'],opts:[{id:'mode',label:'Mode',type:'select',choices:['Beautify','Minify','Validate only'],def:'Beautify'},{id:'indent',label:'Indentation',type:'select',choices:['2 spaces','4 spaces','Tab'],def:'2 spaces'}]},
'xml-formatter':{cat:'text',ic:'xml',label:'XML Formatter',desc:'Beautify, minify and validate XML files.',accept:'.xml,.svg,.txt',multi:false,tips:['Also works on SVG files','Validates structure and reports errors clearly'],opts:[{id:'mode',label:'Mode',type:'select',choices:['Beautify','Minify','Validate only'],def:'Beautify'},{id:'indent',label:'Indentation',type:'select',choices:['2 spaces','4 spaces','Tab'],def:'2 spaces'}]},
'csv-json':{cat:'text',ic:'csv',label:'CSV ↔ JSON',desc:'Convert CSV to JSON or JSON arrays back to CSV.',accept:'.csv,.json,.txt',multi:false,tips:['CSV: first row is treated as the header/keys','JSON: must be an array of objects'],opts:[{id:'direction',label:'Direction',type:'select',choices:['CSV → JSON','JSON → CSV'],def:'CSV → JSON'},{id:'pretty',label:'Pretty Print JSON',type:'toggle',def:true}]},
'html-to-pdf':{cat:'text',ic:'globe',label:'HTML → PDF',desc:'Convert an HTML file to a readable PDF document.',accept:'.html,.htm',multi:false,tips:['Inline styles are best supported','Renders text content — complex CSS may not fully apply'],opts:[{id:'pagesize',label:'Page Size',type:'select',choices:['A4 Portrait','A4 Landscape','Letter Portrait'],def:'A4 Portrait'},{id:'margin',label:'Margin',type:'select',choices:['None','Small (10mm)','Normal (20mm)','Large (30mm)'],def:'Normal (20mm)'}]},
'html-preview':{cat:'text',ic:'html',label:'HTML Preview',desc:'Live preview any HTML file in desktop and mobile views.',accept:'.html,.htm',multi:false,tips:['Desktop and mobile toggle available','Sandboxed iframe for security'],opts:[]},
'invoice-generator':{cat:'text',ic:'invoice',label:'Invoice Generator',desc:'Fill in details and download a professional PDF invoice.',accept:null,multi:false,tips:['Tax is automatically calculated','Supports multiple line items'],opts:[{id:'bizName',label:'Business Name',type:'text',def:''},{id:'bizEmail',label:'Email / Phone',type:'text',def:''},{id:'clientName',label:'Client Name',type:'text',def:''},{id:'invNum',label:'Invoice Number',type:'text',def:'INV-001'},{id:'invDate',label:'Date',type:'text',def:new Date().toLocaleDateString()},{id:'items',label:'Line Items',sub:'Format: Description | Qty | Price — one per line',type:'__ta__',def:'Web Design | 1 | 2500.00\nHosting | 1 | 120.00'},{id:'tax',label:'Tax Rate (%)',type:'number',def:'10'},{id:'notes',label:'Notes',type:'text',def:'Payment due within 30 days'}]},
'meeting-minutes':{cat:'text',ic:'meeting',label:'Meeting Minutes',desc:'Generate structured meeting minutes as a PDF document.',accept:null,multi:false,tips:['Action items appear in a summary table','Use | to separate Action | Owner | Due date'],opts:[{id:'meeting',label:'Meeting Title',type:'text',def:'Team Meeting'},{id:'mdate',label:'Date',type:'text',def:new Date().toLocaleDateString()},{id:'attendees',label:'Attendees',sub:'Comma-separated names',type:'text',def:''},{id:'agenda',label:'Agenda Items',sub:'One item per line',type:'__ta__',def:'1. Review actions\n2. Project update\n3. AOB'},{id:'decisions',label:'Key Decisions',sub:'One decision per line',type:'__ta__',def:''},{id:'actions',label:'Action Items',sub:'Action | Owner | Due — one per line',type:'__ta__',def:'Send report | John | Friday'},{id:'nextMeeting',label:'Next Meeting',type:'text',def:''}]},
'line-sorter':{cat:'text',ic:'text',label:'Line Sorter',desc:'Sort lines alphabetically and remove duplicates.',accept:null,multi:false,tips:['Useful for logs and lists'],opts:[{id:'inputText',label:'Input Text',type:'__ta__',def:''},{id:'desc',label:'Descending',type:'toggle',def:false},{id:'uniq',label:'Unique Lines',type:'toggle',def:false}]},
'whitespace-remover':{cat:'text',ic:'text',label:'Whitespace Remover',desc:'Trim/normalize extra whitespace from text.',accept:null,multi:false,tips:['Great before parsing text'],opts:[{id:'inputText',label:'Input Text',type:'__ta__',def:''}]},
'text-to-hex':{cat:'text',ic:'text',label:'Text to HEX',desc:'Convert text into hexadecimal bytes.',accept:null,multi:false,tips:['UTF-8 encoding'],opts:[{id:'inputText',label:'Input Text',type:'__ta__',def:''}]},
'hex-to-text':{cat:'text',ic:'text',label:'HEX to Text',desc:'Convert hexadecimal bytes back to text.',accept:null,multi:false,tips:['Accepts spaces between bytes'],opts:[{id:'inputText',label:'HEX Input',type:'__ta__',def:''}]},
// ── SECURITY ──────────────────────────────────────────
'password-gen':{cat:'sec',ic:'password',label:'Password Generator',desc:'Generate cryptographically secure passwords with strength meter.',accept:null,multi:false,tips:['12+ characters recommended for most accounts','All character types = maximum strength','Generated using crypto.getRandomValues()'],opts:[{id:'length',label:'Password Length',type:'range',min:6,max:128,step:1,def:16,unit:' chars'},{id:'upper',label:'Uppercase (A–Z)',type:'toggle',def:true},{id:'lower',label:'Lowercase (a–z)',type:'toggle',def:true},{id:'numbers',label:'Numbers (0–9)',type:'toggle',def:true},{id:'symbols',label:'Symbols (!@#$…)',type:'toggle',def:true},{id:'count',label:'How Many to Generate',type:'range',min:1,max:20,step:1,def:5,unit:''}]},
'uuid-gen':{cat:'sec',ic:'uuid',label:'UUID Generator',desc:'Generate UUIDs, ULIDs, NanoIDs and Short IDs in bulk.',accept:null,multi:false,tips:['UUID v4 is cryptographically random','ULIDs are sortable by time — great for databases'],opts:[{id:'type',label:'ID Format',type:'select',choices:['UUID v4','UUID v4 (uppercase)','ULID','NanoID (21 chars)','Short ID (8 chars)'],def:'UUID v4'},{id:'count',label:'How Many',type:'range',min:1,max:200,step:1,def:10,unit:''}]},
'file-hash':{cat:'sec',ic:'hash',label:'File Hash',desc:'Calculate and verify SHA-256, SHA-512, SHA-1 checksums.',accept:'*/*',multi:false,tips:['Paste an expected hash to verify file integrity','SHA-256 is the most widely used standard','Use to detect file tampering or corruption'],opts:[{id:'algos',label:'Algorithms',type:'select',choices:['All (SHA-1 + SHA-256 + SHA-512)','SHA-256 only','SHA-512 only'],def:'All (SHA-1 + SHA-256 + SHA-512)'},{id:'expected',label:'Expected Hash',sub:'Paste here to verify — leave blank to skip',type:'text',def:''}]},
'url-tool':{cat:'sec',ic:'link',label:'URL Encoder',desc:'Encode or decode URLs and URI components.',accept:null,multi:false,tips:['Encode replaces special chars with %XX','Decode converts %XX back to readable text'],opts:[{id:'mode',label:'Mode',type:'select',choices:['Encode URI Component','Decode URI Component','Encode URL','Decode URL'],def:'Encode URI Component'},{id:'inputText',label:'Input Text',type:'__ta__',def:'https://example.com/search?q=hello world&lang=en'}]},
'text-encrypt':{cat:'sec',ic:'encrypt',label:'Text Encrypt / Decrypt',desc:'Encrypt or decrypt text using AES-256-GCM.',accept:null,multi:false,tips:['Password is never stored or sent anywhere','Use the same password to decrypt','AES-256-GCM with PBKDF2 key derivation'],opts:[{id:'mode',label:'Mode',type:'select',choices:['Encrypt','Decrypt'],def:'Encrypt'},{id:'password',label:'Password',type:'password',def:''},{id:'inputText',label:'Input Text',type:'__ta__',def:''}]},
'barcode-gen':{cat:'sec',ic:'barcode',label:'Barcode Generator',desc:'Generate Code128 barcodes as downloadable PNG.',accept:null,multi:false,tips:['Code128 supports all printable ASCII characters','Enable text label to show content below barcode'],opts:[{id:'content',label:'Barcode Content',type:'text',def:'TOOLKIT-001'},{id:'barW',label:'Bar Width',type:'range',min:1,max:4,step:1,def:2,unit:'px'},{id:'barH',label:'Bar Height',type:'range',min:40,max:180,step:10,def:80,unit:'px'},{id:'showText',label:'Show Text Label',type:'toggle',def:true}]},
'metadata-scrubber':{cat:'sec',ic:'meta',label:'Metadata Scrubber',desc:'Strip hidden metadata from images and PDFs before sharing.',accept:'.pdf,image/jpeg,image/png,image/webp',multi:true,tips:['Images: EXIF, GPS, camera info removed','PDFs: author, software, edit history cleared','Output preserves the original file format'],opts:[]},
'file-encrypt':{cat:'sec',ic:'filelock',label:'File Encrypt / Decrypt',desc:'Encrypt any file with AES-256-GCM encryption.',accept:'*/*',multi:false,tips:['Use the exact same password to decrypt','Works on any file type','Output is a .enc file — keep the original filename'],opts:[{id:'mode',label:'Mode',type:'select',choices:['Encrypt file','Decrypt .enc file'],def:'Encrypt file'},{id:'password',label:'Password',type:'password',def:''}]},
// ── MEDIA ──────────────────────────────────────────
'ico-generator':{cat:'media',ic:'favicon',label:'Favicon Generator',desc:'Create multi-size PNG favicons and an ICO file.',accept:'image/*',multi:false,tips:['16, 32, 48px covers all browser needs','An HTML snippet is included to paste into your <head>','Start with a square, high-contrast image for best results'],opts:[{id:'sizes',label:'Sizes to Generate',type:'select',choices:['16, 32, 48px (standard)','16, 32, 48, 64, 128px (full set)','32px only'],def:'16, 32, 48px (standard)'}]},
'svg-optimizer':{cat:'media',ic:'svgopt',label:'SVG Optimizer',desc:'Minify SVG files safely by removing redundant data.',accept:'.svg',multi:true,tips:['Can reduce SVG size by 40–70%','Removes comments, metadata and excess whitespace','Decimal rounding reduces precision safely'],opts:[{id:'removeComments',label:'Remove Comments',type:'toggle',def:true},{id:'removeMetadata',label:'Remove Metadata',type:'toggle',def:true},{id:'roundDecimals',label:'Round Decimals (2dp)',type:'toggle',def:true}]},
'font-preview':{cat:'media',ic:'font',label:'Font Preview',desc:'Preview .ttf, .otf, .woff and .woff2 font files live.',accept:'.ttf,.otf,.woff,.woff2',multi:false,tips:['Renders font live in the browser','CSS @font-face snippet included','Export as PNG specimen sheet'],opts:[{id:'previewText',label:'Preview Text',type:'text',def:'The quick brown fox jumps over the lazy dog'},{id:'fontSize',label:'Preview Size',type:'range',min:12,max:80,step:2,def:36,unit:'px'}]},
'video-thumbnail':{cat:'media',ic:'video',label:'Video Thumbnail',desc:'Extract a specific frame from a video as a JPG.',accept:'video/*',multi:false,tips:['Supports MP4, WebM, MOV and most formats','Seek to any timestamp in seconds'],opts:[{id:'time',label:'Timestamp (seconds)',type:'number',def:'3'},{id:'quality',label:'JPEG Quality',type:'range',min:50,max:100,step:1,def:92,unit:'%'}]},
// ── DEVELOPER ─────────────────────────────────────────
'qr-generator':{cat:'dev',ic:'qr',label:'QR Code Generator',desc:'Generate scannable QR codes from any URL or text.',accept:null,multi:false,tips:['Encodes URLs, Wi-Fi passwords, contact info','High error correction works even if QR is partially obscured','Download as PNG for print or digital use'],opts:[{id:'content',label:'Content',sub:'URL, text, Wi-Fi password, or any data',type:'text',def:'https://example.com'},{id:'size',label:'Image Size',type:'select',choices:['256×256','512×512','1024×1024'],def:'512×512'},{id:'errLevel',label:'Error Correction',type:'select',choices:['Low (7%)','Medium (15%)','High (30%)'],def:'Medium (15%)'}]},
'timestamp':{cat:'dev',ic:'clock',label:'Timestamp Converter',desc:'Convert Unix timestamps to readable dates and vice versa.',accept:null,multi:false,tips:['Unix = seconds since Jan 1, 1970 UTC','JavaScript uses milliseconds (13-digit numbers)','Use the date picker in Date → Unix mode'],opts:[{id:'mode',label:'Mode',type:'select',choices:['Now (current timestamp)','Unix → Date (seconds)','Unix → Date (ms)','Date → Unix'],def:'Now (current timestamp)'},{id:'inputVal',label:'Unix Timestamp',sub:'Enter a Unix value here (seconds or ms)',type:'text',def:''},{id:'dateInput',label:'Date & Time',sub:'Pick a date/time for Date → Unix conversion',type:'datetime',def:''}]},
'regex-tester':{cat:'dev',ic:'regex',label:'Regex Tester',desc:'Test regular expressions with live match highlighting.',accept:null,multi:false,tips:['Results update live as you type','Quick-insert common patterns below','g=global, i=ignore case, m=multiline flags'],opts:[{id:'pattern',label:'Pattern',sub:'Without slashes — updates live as you type',type:'text',def:'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}'},{id:'flags',label:'Flags',type:'select',choices:['gi — global, ignore case','g — global','i — ignore case','gm — global, multiline'],def:'gi — global, ignore case'},{id:'testText',label:'Test Text',sub:'Type here — matches are highlighted instantly',type:'__ta__',def:'Test: user@example.com, admin@company.org, not-valid@'}]},
'diff-checker':{cat:'dev',ic:'diff',label:'Diff Checker',desc:'Compare two text blocks and highlight all changes.',accept:null,multi:false,tips:['Green = added lines, Red = removed lines','Word-level changes shown on modified lines','Download a plain text diff report'],opts:[]},
'url-parser':{cat:'dev',ic:'link',label:'URL Parser',desc:'Parse URL into protocol, host, path, query, and hash.',accept:null,multi:false,tips:['Quick query breakdown'],opts:[{id:'inputText',label:'URL',type:'text',def:'https://example.com/a?x=1#top'}]},
'url-decoder':{cat:'dev',ic:'link',label:'URL Decoder',desc:'Decode percent-encoded URL content.',accept:null,multi:false,tips:['Converts %XX escapes'],opts:[{id:'inputText',label:'Encoded URL',type:'__ta__',def:''}]},
'slug-generator':{cat:'dev',ic:'text',label:'Slug Generator',desc:'Generate SEO-friendly slug from text.',accept:null,multi:false,tips:['lowercase-hyphen-format'],opts:[{id:'inputText',label:'Input Text',type:'text',def:''}]},
'my-user-agent':{cat:'dev',ic:'info',label:'My User Agent',desc:'Display the current browser user agent string.',accept:null,multi:false,tips:['Useful for debugging'],opts:[]},
'my-ip':{cat:'dev',ic:'globe',label:'My IP',desc:'Fetch and display your public IP address.',accept:null,multi:false,tips:['Requires internet access'],opts:[]},
'keyboard-test':{cat:'dev',ic:'text',label:'Keyboard Testing',desc:'Live key press tester for Windows & Mac keyboards.',accept:null,multi:false,tips:['Press any key to see key/code'],opts:[]},
'touchpad-test':{cat:'dev',ic:'text',label:'Touch Pad Testing',desc:'Pointer movement/click tester for touchpads.',accept:null,multi:false,tips:['Move/click in test area'],opts:[]},
// ── UTILITIES ─────────────────────────────────────────
'pdf-text':{cat:'util',ic:'text',label:'Extract PDF Text',desc:'Pull all readable text from any text-based PDF.',accept:'.pdf',multi:false,tips:['Only works on text-based PDFs (not scanned)','Add page separators to identify page breaks'],opts:[{id:'fmt',label:'Output Format',type:'select',choices:['Plain Text (.txt)','Markdown (.md)'],def:'Plain Text (.txt)'},{id:'pagebreaks',label:'Add Page Separators',type:'toggle',def:true}]},
'bulk-rename':{cat:'util',ic:'rename',label:'Bulk Rename',desc:'Rename multiple files with a prefix and sequential numbers.',accept:'*/*',multi:true,tips:['Files are packed into a ZIP with new names','Original files are never modified'],opts:[{id:'prefix',label:'Filename Prefix',type:'text',def:'Document_'},{id:'start',label:'Start Number',type:'number',def:'1'},{id:'pad',label:'Number Format',type:'select',choices:['001 (3 digits)','01 (2 digits)','1 (no padding)'],def:'001 (3 digits)'}]},
'img-to-portfolio':{cat:'util',ic:'portfolio',label:'Image Portfolio',desc:'Combine images into a titled, cover-paged PDF portfolio.',accept:'image/*',multi:true,tips:['A professional cover page is added automatically','Each image fills one page'],opts:[{id:'title',label:'Portfolio Title',type:'text',def:'Image Portfolio'},{id:'author',label:'Prepared By',type:'text',def:''},{id:'pagesize',label:'Page Size',type:'select',choices:['A4 Portrait','A4 Landscape','Letter Portrait'],def:'A4 Portrait'}]},
'word-counter':{cat:'util',ic:'wordcount',label:'Word Counter',desc:'Count words, characters, sentences and estimated reading time. Supports TXT, MD, PDF and DOCX.',accept:'.txt,.md,.pdf,.csv,.docx,.doc',multi:false,tips:['Works with TXT, Markdown, PDF and DOCX files','Shows reading and speaking time estimates','Reports unique word count and average word length'],opts:[]},
'csv-to-pdf':{cat:'util',ic:'csv',label:'CSV → PDF Table',desc:'Convert CSV data into a clean, formatted PDF table.',accept:'.csv',multi:false,tips:['First row is treated as the table header','Alternating row colours for readability'],opts:[{id:'title',label:'Report Title',type:'text',def:'Data Report'},{id:'fontsize',label:'Font Size',type:'select',choices:['8pt — Very compact','10pt — Compact','12pt — Normal'],def:'10pt — Compact'}]},
'base64-tool':{cat:'util',ic:'base64',label:'Base64 Encoder',desc:'Encode any file to Base64 text or decode Base64 back to a file.',accept:'*/*',multi:false,tips:['Common in APIs, emails and data URIs','Output is a plain text file with Base64 content'],opts:[{id:'mode',label:'Mode',type:'select',choices:['Encode file → Base64','Decode Base64 → file'],def:'Encode file → Base64'}]},
'html-encode':{cat:'util',ic:'html',label:'HTML Encoder',desc:'Encode text for safe HTML rendering.',accept:null,multi:false,tips:['Escapes special chars'],opts:[{id:'inputText',label:'Input',type:'__ta__',def:''}]},
'html-decode':{cat:'util',ic:'html',label:'HTML Decoder',desc:'Decode HTML entities into plain text.',accept:null,multi:false,tips:['Converts & etc.'],opts:[{id:'inputText',label:'Input',type:'__ta__',def:''}]},
'html-stripper':{cat:'util',ic:'html',label:'HTML Stripper',desc:'Strip HTML tags and keep plain text.',accept:null,multi:false,tips:['Removes markup only'],opts:[{id:'inputText',label:'Input HTML',type:'__ta__',def:''}]},
'base32-encoder':{cat:'util',ic:'base64',label:'Base32 Encoder',desc:'Encode text as Base32.',accept:null,multi:false,tips:['RFC4648 alphabet'],opts:[{id:'inputText',label:'Input',type:'__ta__',def:''}]},
'base32-decoder':{cat:'util',ic:'base64',label:'Base32 Decoder',desc:'Decode Base32 text.',accept:null,multi:false,tips:['Accepts padded input'],opts:[{id:'inputText',label:'Input',type:'__ta__',def:''}]},
'passphrase-generator':{cat:'util',ic:'password',label:'Passphrase Generator',desc:'Generate memorable, strong passphrases from a 200+ word pool.',accept:null,multi:false,tips:['4+ words is strong enough for most accounts','Use a separator like - or . for readability','Capitalize first letter for extra strength','Generate multiple and pick your favourite'],opts:[{id:'count',label:'Words per Phrase',type:'range',min:2,max:8,step:1,def:4,unit:' words'},{id:'num',label:'How Many to Generate',type:'range',min:1,max:20,step:1,def:5,unit:''},{id:'sep',label:'Separator',type:'select',choices:['-','.','+','_','~','(space)'],def:'-'},{id:'cap',label:'Capitalize Words',type:'toggle',def:false}]},
'pin-generator':{cat:'util',ic:'password',label:'PIN Generator',desc:'Generate secure random PINs of any length, with copy buttons for each.',accept:null,multi:false,tips:['4–6 digits for most ATM/phone uses','8–12 digits for high-security applications','Group by 4 makes long PINs easier to read'],opts:[{id:'digits',label:'Digits',type:'range',min:4,max:12,step:1,def:6,unit:' digits'},{id:'count',label:'How Many to Generate',type:'range',min:1,max:50,step:1,def:10,unit:''},{id:'groups',label:'Group by 4 digits',type:'toggle',def:false}]},
'utm-builder':{cat:'dev',ic:'globe',label:'UTM Builder',desc:'Build campaign-tracking URLs with UTM parameters.',accept:null,multi:false,tips:['Use consistent naming across campaigns','Auto URL encoding is applied'],opts:[{id:'utmBase',label:'Base URL',type:'text',def:'https://example.com/landing'},{id:'utmSource',label:'utm_source',type:'text',def:''},{id:'utmMedium',label:'utm_medium',type:'text',def:''},{id:'utmCampaign',label:'utm_campaign',type:'text',def:''},{id:'utmTerm',label:'utm_term (optional)',type:'text',def:''},{id:'utmContent',label:'utm_content (optional)',type:'text',def:''}]},
'translate-text':{cat:'dev',ic:'text',label:'Multi-language Translate',desc:'Translate text between major languages using AI. Fast, accurate, no sign-up required.',accept:null,multi:false,tips:['Supports 10+ languages including Chinese, Japanese, Arabic','AI-powered — requires internet connection','Output is copied to clipboard and downloadable'],opts:[{id:'trSource',label:'Source Language',type:'select',choices:['Auto','English','Chinese (Simplified)','Chinese (Traditional)','Japanese','Korean','Spanish','French','German','Portuguese','Arabic'],def:'Auto'},{id:'trTarget',label:'Target Language',type:'select',choices:['English','Chinese (Simplified)','Chinese (Traditional)','Japanese','Korean','Spanish','French','German','Portuguese','Arabic'],def:'Spanish'},{id:'trText',label:'Input Text',type:'__ta__',def:''}]},
};
const GRIDS = {
pdf: ['compress-pdf','merge-pdf','split-pdf','rotate-pdf','pdf-to-jpg','jpg-to-pdf','unlock-pdf','protect-pdf','add-page-numbers','stamp-pdf','pdf-info','delete-pdf-pages','duplicate-pdf-pages','reorder-pdf','pdf-header-footer','flatten-pdf','pdf-thumbnail','pdf-to-csv','pdf-bookmarks','pdf-to-word','word-to-pdf','ocr-pdf'],
img: ['compress-image','resize-image','crop-image','convert-to-jpg','convert-from-jpg','grayscale','watermark-image','flip-image','image-borders','round-corners','blur-image','exif-tool','colour-palette','image-collage','image-info','sprite-sheet'],
text: ['text-case','lorem-ipsum','find-replace','json-formatter','xml-formatter','csv-json','html-to-pdf','html-preview','invoice-generator','meeting-minutes','line-sorter','whitespace-remover','text-to-hex','hex-to-text'],
sec: ['password-gen','uuid-gen','file-hash','url-tool','text-encrypt','barcode-gen','metadata-scrubber','file-encrypt'],
media: ['ico-generator','svg-optimizer','font-preview','video-thumbnail'],
dev: ['qr-generator','timestamp','regex-tester','diff-checker','utm-builder','translate-text','url-parser','url-decoder','slug-generator','my-user-agent','my-ip','keyboard-test','touchpad-test'],
util: ['pdf-text','bulk-rename','img-to-portfolio','word-counter','csv-to-pdf','base64-tool','html-encode','html-decode','html-stripper','base32-encoder','base32-decoder','passphrase-generator','pin-generator'],
};
const NEW_TOOLS = new Set(['html-preview','diff-checker','barcode-gen','file-encrypt','metadata-scrubber','sprite-sheet','image-collage','blur-image','round-corners','colour-palette','uuid-gen']);
const NO_FILE = new Set(['text-case','lorem-ipsum','url-tool','text-encrypt','password-gen','uuid-gen','timestamp','regex-tester','diff-checker','invoice-generator','meeting-minutes','barcode-gen','qr-generator','html-preview','utm-builder','translate-text','line-sorter','whitespace-remover','text-to-hex','hex-to-text','url-parser','url-decoder','slug-generator','my-user-agent','my-ip','keyboard-test','touchpad-test','html-encode','html-decode','html-stripper','base32-encoder','base32-decoder','passphrase-generator','pin-generator']);
// CAT must live here — tools.js runs before app.js
const CAT = {
pdf: { bg:'rgba(255,92,92,.15)', fg:'#ff5c5c', cls:'cpdf' },
img: { bg:'rgba(255,159,67,.15)', fg:'#ff9f43', cls:'cimg' },
text: { bg:'rgba(124,111,255,.15)', fg:'#7c6fff', cls:'ctext' },
sec: { bg:'rgba(84,160,255,.15)', fg:'#54a0ff', cls:'csec' },
media:{ bg:'rgba(31,204,170,.15)', fg:'#1fccaa', cls:'cmedia' },
dev: { bg:'rgba(255,210,42,.15)', fg:'#ffd32a', cls:'cdev' },
util: { bg:'rgba(124,111,255,.15)', fg:'#7c6fff', cls:'cutil' },
};
function buildCard(id) {
const t = TOOLS[id]; if (!t) return '';
const c = CAT[t.cat] || CAT.util;
const svg = IC[t.ic] || IC.pdf;
return `<div class="tc" onclick="openTool('${id}')" data-label="${t.label.toLowerCase()}" data-desc="${t.desc.toLowerCase()}" data-cat="${t.cat}">
<div class="tc-top">
<div class="tc-ic ${c.cls}" style="background:${c.bg};color:${c.fg}">${svg}</div>
${NEW_TOOLS.has(id) ? '<span class="tc-new">New</span>' : ''}
</div>
<h3>${t.label}</h3>
<p>${t.desc}</p>
</div>`;
}
// Defer grid build until DOM is ready
document.addEventListener('DOMContentLoaded', () => {
Object.entries(GRIDS).forEach(([cat, ids]) => {
const el = document.getElementById('grid-' + cat);
if (el) el.innerHTML = ids.map(buildCard).join('');
});
});