Get Started
NOW
Receive your estimate
USV Label Maker
Label Maker
Order Info
Blind Details
Track
Valance
IB = Inside Mount
OB = Outside Mount
Location
Quick Add
Add same order multiple times (blind # auto-increments)
1
blind(s)
📄

Drop PDF here or tap to browse

Order forms, work orders, invoices

How it works
1 Upload your order PDF
2 AI extracts all blind details — you'll be prompted for anything missing
3 Labels are auto-generated and saved to Google Drive

Label Queue

0
🏷

No labels in queue

Fill out the form or upload a PDF to get started

📁 Saved Jobs

📂

No saved jobs yet

Jobs save automatically when added to the print queue

Connect Google Drive to enable job history

'); win.document.close(); win.onload = function() { win.focus(); win.print(); }; setTimeout(function() { try { win.focus(); win.print(); } catch(e){} }, 900); } function printAll() { if (!queue.length) return; const labels = []; queue.forEach(function(b) { getLabelTabs(b.type).forEach(function(t) { labels.push(buildLabelHTML(b, t.key)); }); }); openPrintWindow(labels); } function printSingleLabel(groupIdx, labelType) { const b = queue[groupIdx]; if (!b) return; openPrintWindow([buildLabelHTML(b, labelType)]); } function reprintGroup(i) { openReprintModal(queue[i], i); } // ── REPRINT MODAL ─────────────────────────────────────────────────── function openReprintModal(blindData, queueIdx) { const tabs = getLabelTabs(blindData.type); document.getElementById('reprint-modal-title').textContent = `Reprint — ${blindData.customer} Blind ${blindData.blindNum}/${blindData.blindTotal}`; document.getElementById('reprint-modal-sub').textContent = `Select a label to reprint. It will be marked as REPRINT.`; const grid = document.getElementById('reprint-labels-grid'); grid.innerHTML = tabs.map(t => `
${t.label}
${buildLabelHTML(blindData, t.key, true)}
`).join(''); document.getElementById('reprint-modal').classList.add('open'); } function doPrintReprint(groupIdx, labelType) { closeReprintModal(); const b = groupIdx >= 0 ? queue[groupIdx] : (window._reprintFromHistory && window._reprintFromHistory.blind); if (!b) return; setTimeout(function() { openPrintWindow([buildLabelHTML(b, labelType, true)]); }, 300); } function closeReprintModal() { document.getElementById('reprint-modal').classList.remove('open'); } // ── FORM HELPERS ─────────────────────────────────────────────────── // Clears per-blind fields only — keeps customer/dealer/order/date/total intact function clearBlindFields() { ['f-width','f-drop','f-material','f-room','f-comment'].forEach(id => { document.getElementById(id).value = ''; }); const vf = document.getElementById('f-valwidth'); vf.value = ''; delete vf.dataset.userEdited; document.getElementById('f-carriers').value = ''; document.getElementById('f-type').value = 'COMPLETE'; document.getElementById('f-control').value = 'ONE-WAY'; setReturn('IB'); onTypeChange(); } // Full reset — everything function clearForm() { ['f-dealer','f-customer','f-order','f-width','f-drop','f-material','f-room','f-comment','f-valwidth'].forEach(id => { const el = document.getElementById(id); el.value = ''; delete el.dataset.userEdited; }); document.getElementById('f-date').value = today(); document.getElementById('f-blindnum').value = 1; document.getElementById('f-blindtotal').value = 1; document.getElementById('f-type').value = 'COMPLETE'; document.getElementById('f-control').value = 'ONE-WAY'; document.getElementById('f-carriers').value = ''; setReturn('IB'); onTypeChange(); addCount = 1; document.getElementById('add-count').textContent = '1'; currentJobId = null; currentJobDriveFileId = null; } // ── PDF UPLOAD ────────────────────────────────────────────────────── function setupDragDrop() { const zone = document.getElementById('upload-zone'); zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('drag-over'); }); zone.addEventListener('dragleave', () => zone.classList.remove('drag-over')); zone.addEventListener('drop', e => { e.preventDefault(); zone.classList.remove('drag-over'); const f = e.dataTransfer.files[0]; if (f?.type === 'application/pdf') processPdf(f); }); } function handlePdfUpload(e) { const f = e.target.files[0]; if (f) processPdf(f); e.target.value = ''; } function setPdfStatus(msg, type) { const el = document.getElementById('pdf-status'); el.textContent = msg; el.className = `pdf-status visible ${type}`; } async function processPdf(file) { setPdfStatus('⏳ Reading PDF...', 'loading'); try { const base64 = await fileToBase64(file); setPdfStatus('🤖 Extracting order data with AI...', 'loading'); const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 3000, messages: [{ role: 'user', content: [ { type: 'document', source: { type: 'base64', media_type: 'application/pdf', data: base64 } }, { type: 'text', text: `Extract all vertical blind order information from this PDF. Return ONLY a valid JSON object with these exact keys (no markdown, no backticks): { "dealer": string, "customer": string (UPPERCASE), "orderNum": string (look for order number starting with "O-"), "date": string (YYYY-MM-DD), "blinds": [ { "blindNum": number, "blindTotal": number, "type": "COMPLETE"|"SLATS"|"TRACK"|"VALANCE", "width": string (e.g. "35 1/2"), "drop": string, "material": string (UPPERCASE), "control": "ONE-WAY"|"SPLIT", "valWidth": string (window width + 1 inch, or "NO"), "returns": "IB"|"OB", "room": string (UPPERCASE, or ""), "comment": string (UPPERCASE, or "") } ], "missingFields": ["list","of","fields","that","are","unknown"] } Use empty string "" for unknown text fields. Use null for unknown numbers. List any truly unknown fields in missingFields array.` } ] }] }) }); const data = await response.json(); if (data.error) throw new Error(data.error.message); const text = data.content.find(b => b.type === 'text')?.text || ''; const clean = text.replace(/```json|```/g, '').trim(); const parsed = JSON.parse(clean.match(/\{[\s\S]*\}/)?.[0] || clean); if (!parsed.blinds?.length) throw new Error('No blind orders found in the PDF'); setPdfStatus(`✅ Found ${parsed.blinds.length} blind(s). Checking for missing info...`, 'success'); // Check for missing fields const missing = parsed.missingFields || []; const criticalMissing = missing.filter(f => ['customer','width','drop','blindTotal'].includes(f)); if (criticalMissing.length > 0) { pendingPdfData = parsed; openMissingModal(parsed, criticalMissing); } else { applyPdfData(parsed); } } catch(err) { console.error(err); setPdfStatus(`❌ Error: ${err.message}`, 'error'); } } function fileToBase64(file) { return new Promise((res, rej) => { const r = new FileReader(); r.onload = () => res(r.result.split(',')[1]); r.onerror = rej; r.readAsDataURL(file); }); } // ── MISSING FIELDS MODAL ───────────────────────────────────────────── const FIELD_LABELS = { customer: 'Customer Name', orderNum: 'Order Number (e.g. O-12345)', width: 'Width (e.g. 35 1/2)', drop: 'Drop / Length', blindTotal: 'Total # of Blinds in Order', material: 'Material / Style', room: 'Room / Location', }; function openMissingModal(pdfData, missingList) { const form = document.getElementById('missing-fields-form'); form.innerHTML = missingList.map(f => `
`).join(''); form.dataset.missing = JSON.stringify(missingList); document.getElementById('missing-modal').classList.add('open'); } function closeMissingModal() { document.getElementById('missing-modal').classList.remove('open'); pendingPdfData = null; } function submitMissingFields() { if (!pendingPdfData) return; const missing = JSON.parse(document.getElementById('missing-fields-form').dataset.missing || '[]'); const overrides = {}; for (const f of missing) { const val = document.getElementById(`mf-${f}`)?.value?.trim(); if (val) overrides[f] = val.toUpperCase(); } const merged = { ...pendingPdfData, ...overrides }; // Apply overrides to each blind too merged.blinds = merged.blinds.map(b => ({ ...b, ...overrides })); closeMissingModal(); applyPdfData(merged); } function applyPdfData(data) { const blinds = data.blinds || []; for (const b of blinds) { const blindData = { dealer: (data.dealer || 'USV').toUpperCase(), customer: (data.customer || b.customer || '').toUpperCase(), orderNum: (data.orderNum || '').toUpperCase(), date: data.date || today(), blindNum: b.blindNum || 1, blindTotal: b.blindTotal || blinds.length, type: b.type || 'COMPLETE', width: b.width || '', drop: b.drop || '', material: (b.material || '').toUpperCase(), control: b.control || 'ONE-WAY', carriers: calcCarriers(b.width, b.control || 'ONE-WAY'), valWidth: b.valWidth || (parseFraction(b.width) ? formatFraction(parseFraction(b.width) + 1) : ''), returns: b.returns || 'IB', room: (b.room || '').toUpperCase(), comment: (b.comment || '').toUpperCase(), }; queue.push(blindData); } renderQueue(); setPdfStatus(`✅ ${blinds.length} label group(s) added to queue`, 'success'); switchTab('manual'); showView('maker'); autoSaveJob(); showToast(`✅ ${blinds.length} blind(s) imported from PDF`, 'success'); } // ── SAVE JOB ──────────────────────────────────────────────────────── function buildJobSnapshot() { if (!queue.length) return null; const b0 = queue[0]; const safeName = (b0.customer || 'UNKNOWN').replace(/[^A-Z0-9]/g,'_'); const safeDate = (b0.date || '').replace(/-/g,''); const safeOrder = b0.orderNum || 'NO-ORDER'; const filename = safeName + '_' + safeDate + '_' + safeOrder + '.json'; if (!currentJobId) currentJobId = Date.now().toString(); return { id: currentJobId, filename, savedAt: new Date().toISOString(), customer: b0.customer, orderNum: b0.orderNum, date: b0.date, dealer: b0.dealer, blindCount: queue.length, blinds: queue.map(b => ({ ...b })) }; } function autoSaveJob() { const job = buildJobSnapshot(); if (!job) return; saveJobToGDrive(job); } function saveWithoutPrinting() { const job = buildJobSnapshot(); if (!job) { showToast('No labels in queue to save', 'error'); return; } saveJobToGDrive(job, true); } // ── GOOGLE DRIVE ───────────────────────────────────────────────────── function initGDrive() { // Load gapi client for Drive API calls (no auth2) gapi.load('client', async () => { try { await gapi.client.init({ discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'] }); setDriveStatus('disconnected'); } catch(e) { console.error('gapi init error', e); } }); // Set up GIS token client for sign-in popup gisTokenClient = google.accounts.oauth2.initTokenClient({ client_id: GOOGLE_CLIENT_ID, scope: SCOPES, callback: async (resp) => { if (resp.error) { setDriveStatus('error'); showToast('Google Drive sign-in failed', 'error'); return; } gdriveAccessToken = resp.access_token; gapi.client.setToken({ access_token: gdriveAccessToken }); await onDriveSignedIn(); } }); } function connectGDrive() { if (!gisTokenClient) { showToast('Auth not ready, please wait', 'error'); return; } gisTokenClient.requestAccessToken({ prompt: 'consent' }); } async function onDriveSignedIn() { gdriveReady = true; setDriveStatus('connected'); gdriveFolderId = await getOrCreateFolder(GDRIVE_FOLDER_NAME); showToast('Google Drive connected', 'success'); if (document.getElementById('view-history').classList.contains('active')) loadJobHistory(); } function setDriveStatus(state) { const dot = document.getElementById('gdrive-dot'); const lbl = document.getElementById('gdrive-label'); if (state === 'connected') { dot.className='gdrive-dot connected'; lbl.style.display=''; lbl.textContent='Drive'; } else if (state === 'error') { dot.className='gdrive-dot error'; lbl.style.display=''; lbl.textContent='Drive'; } else if (state === 'disconnected') { dot.className='gdrive-dot'; lbl.style.display=''; lbl.textContent='Drive'; } else { dot.className='gdrive-dot'; lbl.style.display='none'; } } async function getOrCreateFolder(name) { try { const res = await gapi.client.drive.files.list({ q: "name='" + name + "' and mimeType='application/vnd.google-apps.folder' and trashed=false", fields: 'files(id,name)' }); if (res.result.files.length > 0) return res.result.files[0].id; const created = await gapi.client.drive.files.create({ resource: { name, mimeType: 'application/vnd.google-apps.folder' }, fields: 'id' }); return created.result.id; } catch(e) { console.error('Folder error', e); return null; } } async function saveJobToGDrive(job, manual = false) { saveJobLocal(job); if (!gdriveReady || !gdriveAccessToken) { if (manual) showToast('Saved locally (Drive not connected)', 'info'); return; } try { const body = JSON.stringify(job, null, 2); if (currentJobDriveFileId) { // Update existing file — no duplicate created await fetch('https://www.googleapis.com/upload/drive/v3/files/' + currentJobDriveFileId + '?uploadType=media', { method: 'PATCH', headers: { Authorization: 'Bearer ' + gdriveAccessToken, 'Content-Type': 'application/json' }, body }); } else { // First save for this order — create new file const metadata = { name: job.filename, mimeType: 'application/json', parents: gdriveFolderId ? [gdriveFolderId] : [] }; const form = new FormData(); form.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' })); form.append('file', new Blob([body], { type: 'application/json' })); const res = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id', { method: 'POST', headers: { Authorization: 'Bearer ' + gdriveAccessToken }, body: form }); const created = await res.json(); if (created.id) currentJobDriveFileId = created.id; } if (manual) showToast('Job saved to Google Drive', 'success'); } catch(e) { console.error('Drive save error', e); if (manual) showToast('Saved locally (Drive error)', 'info'); } } // ── LOCAL STORAGE FALLBACK ─────────────────────────────────────────── function saveJobLocal(job) { const jobs = JSON.parse(localStorage.getItem('usv_jobs') || '[]'); const others = jobs.filter(j => j.id !== job.id); others.unshift(job); localStorage.setItem('usv_jobs', JSON.stringify(others.slice(0, 200))); } function getLocalJobs() { return JSON.parse(localStorage.getItem('usv_jobs') || '[]'); } // ── LOAD JOB HISTORY ───────────────────────────────────────────────── async function loadJobHistory() { const area = document.getElementById('history-area'); area.innerHTML = '
Loading jobs...
'; let jobs = []; if (gdriveReady && gdriveFolderId && gdriveAccessToken) { try { const res = await gapi.client.drive.files.list({ q: "'" + gdriveFolderId + "' in parents and mimeType='application/json' and trashed=false", fields: 'files(id,name,createdTime)', orderBy: 'createdTime desc', pageSize: 200 }); for (const f of (res.result.files || [])) { try { const r = await fetch('https://www.googleapis.com/drive/v3/files/' + f.id + '?alt=media', { headers: { Authorization: 'Bearer ' + gdriveAccessToken } }); jobs.push(await r.json()); } catch(e) {} } } catch(e) { console.error('Drive load error', e); } } const seen = new Set(); const all = [...jobs, ...getLocalJobs()].filter(j => { if (!j.id||seen.has(j.id)) return false; seen.add(j.id); return true; }); renderJobHistory(all); } function renderJobHistory(jobs) { const area = document.getElementById('history-area'); if (!jobs.length) { area.innerHTML = `
📂

No saved jobs yet

Jobs auto-save when added to the print queue

${!gdriveReady ? `` : ''}
`; return; } area.innerHTML = jobs.map((job, i) => `
${formatDate(job.date)}
${job.customer || 'Unknown'}
${job.orderNum || 'No Order #'}  |  ${job.blindCount || job.blinds?.length || 0} blind(s)  |  ${formatDateTime(job.savedAt)}
${job.dealer||''}  |  Saved: ${formatDateTime(job.savedAt)}
${(job.blinds||[]).map((b,bi) => `
Blind ${b.blindNum}/${b.blindTotal} — ${b.width||''}×${b.drop||''}" ${b.material||''}
`).join('')}
`).join(''); // Store jobs for later reference window._historyJobs = jobs; } function toggleJob(i) { const card = document.getElementById(`jcard-${i}`); card.classList.toggle('open'); } function reloadJob(i) { const job = window._historyJobs?.[i]; if (!job) return; if (queue.length && !confirm('This will replace your current queue. Continue?')) return; queue = (job.blinds || []).map(b => ({ ...b })); renderQueue(); showView('maker'); showToast('Job loaded to queue', 'info'); } function reprintEntireJob(i) { const job = window._historyJobs?.[i]; if (!job) return; // Load to queue temporarily and print const prev = [...queue]; queue = (job.blinds || []).map(b => ({ ...b })); renderQueue(); setTimeout(() => { printAll(); queue = prev; renderQueue(); }, 300); } function openJobBlindReprint(jobIdx, blindIdx) { const job = window._historyJobs?.[jobIdx]; if (!job) return; const blind = job.blinds?.[blindIdx]; if (!blind) return; openReprintModal(blind, -1); // Override doPrintReprint for history context window._reprintFromHistory = { blind, jobIdx, blindIdx }; } function formatDateTime(iso) { if (!iso) return '—'; const d = new Date(iso); return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) + ' ' + d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); } // ── TOAST ──────────────────────────────────────────────────────────── function showToast(msg, type = 'info') { const el = document.getElementById('toast'); el.textContent = msg; el.className = `toast ${type} show`; clearTimeout(toastTimer); toastTimer = setTimeout(() => { el.classList.remove('show'); }, 3500); } // ── CONTROL CHANGE ───────────────────────────────────────────────────



Special Offer
FREE TEMPORARY SHADES WITH EVERY ORDER
GET THIS DEAL



US Verticals Service

Albert G.


After lots of call to other companies, US Verticals was the least expensive with a much better product. The Shutters…

US Verticals Service

Ali R.


The whole process from A to Z when I was dealing with US verticals was seamless and hassle free. I…

US Verticals Service

Andy See


The techs came and took measurement and gave us a quote which is very reasonable. It was $928 plus tax.…

US Verticals Service

Anne Marie E.


From the initial call and scheduling an appointment, Kevin and Dan arrived promptly, explaining the difference in their products, taking…

US Verticals Service

Birma Montes


Very friendly and professional service. The assistance was great since we visited the store until the installation of the verticals…

US Verticals Service

Carlos Level


The word perfect, it is not enough. The work was excellent. On time, and a perfect and fast job.

US Verticals Service

Christine Batista


Re-did Vertical Blinds in living room and formal dining area. This time around we decided to not only change the…

US Verticals Service

Donna Becherer


As always, these folks do a great job! Thanks for keeping the quality and service topnotch.

US Verticals Service

Eric Goldman


We recently purchased a new home and saw US VERTICALS at a neighbors house. I stopped by and asked if…




guarantee
Ask about our unconditional
Price Match Guarantee
See Details



icon Always on Time
icon Honest PRICING
icon 25+ Years
icon Best Warranty