Hybrid PDF + Image + OCR · Self-Learning
Upload Invoice
📂
Drop files here
PDF, PNG, JPG, WEBP, BMP, TIFF
PDF PNG JPG WEBP TIFF
Pages
🧾
Upload an invoice and scan it
Multi-page PDFs and batch mode supported
0 invoices scanned and ready to export
Scanning Invoice
Please wait...
Initializing...
.lang-select { background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px; font-family: 'DM Mono', monospace; font-size: 10px; outline: none; } .lang-select:focus { border-color: var(--accent); } .badge b { color: var(--accent); } /* ── Mode tabs ── */ .mode-tabs { display: flex; gap: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; } .mode-tab { padding: 7px 16px; font-family: 'Syne', sans-serif; font-weight: 600; font-size: 11px; letter-spacing: 1px; text-transform: uppercase; background: none; border: none; color: var(--muted); cursor: pointer; transition: all 0.15s; } .mode-tab.active { background: var(--accent); color: #0f0f0f; } .mode-tab:not(.active):hover { color: var(--text); background: var(--surface2); } /* ── Layout ── */ main { display: grid; grid-template-columns: 380px minmax(0, 1fr) var(--preview-col, 480px); min-height: calc(100vh - 57px); transition: grid-template-columns 220ms ease; } @media (max-width: 1280px) { main { --preview-col: 380px; } } @media (max-width: 1100px) { main { --preview-col: 320px; } } /* When preview is collapsed, shrink its column to a thin reopen handle. */ main.preview-collapsed { --preview-col: 36px; } @media (max-width: 860px) { main { grid-template-columns: 1fr; } /* On mobile, preview overlays as a slide-in drawer instead of a column. */ .preview-panel { position: fixed; right: 0; top: 57px; bottom: 0; width: min(92vw, 480px); z-index: 30; transform: translateX(100%); transition: transform 220ms ease; box-shadow: -8px 0 24px rgba(0,0,0,0.4); } main:not(.preview-collapsed) .preview-panel { transform: translateX(0); } main.preview-collapsed .preview-panel { transform: translateX(100%); } } /* ── Left panel ── */ .left-panel { border-right: 1px solid var(--border); display: flex; flex-direction: column; overflow: hidden; } .panel-section { padding: 24px; border-bottom: 1px solid var(--border); } .panel-label { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); margin-bottom: 14px; } /* ── Drop zone ── */ .drop-zone { border: 1.5px dashed var(--border); border-radius: 10px; padding: 32px 20px; text-align: center; cursor: pointer; transition: all 0.2s; position: relative; background: var(--surface); } .drop-zone:hover, .drop-zone.drag-over { border-color: var(--accent); background: rgba(200,245,66,0.03); } .drop-zone input { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; } .drop-icon { font-size: 28px; margin-bottom: 10px; } .drop-label { font-family: 'Syne', sans-serif; font-weight: 600; font-size: 14px; margin-bottom: 4px; } .drop-sub { color: var(--muted); font-size: 11px; margin-bottom: 12px; } .filetypes { display: flex; gap: 5px; justify-content: center; flex-wrap: wrap; } .ft-pill { background: var(--surface2); border: 1px solid var(--border); border-radius: 3px; padding: 2px 7px; font-size: 10px; color: var(--accent2); letter-spacing: 1px; } /* ── Batch queue ── */ .batch-queue { flex: 1; overflow-y: auto; } .queue-empty { padding: 32px 24px; text-align: center; color: var(--muted); font-size: 12px; } .queue-item { display: flex; align-items: center; gap: 10px; padding: 12px 20px; border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.15s; position: relative; } .queue-item:hover { background: var(--surface); } .queue-item.active { background: var(--surface2); border-left: 2px solid var(--accent); padding-left: 18px; } .queue-item.done { border-left: 2px solid var(--accent2); padding-left: 18px; } .queue-item.error { border-left: 2px solid var(--danger); padding-left: 18px; } .queue-item.scanning { border-left: 2px solid var(--warn); padding-left: 18px; } .qi-icon { font-size: 18px; flex-shrink: 0; } .qi-info { flex: 1; min-width: 0; } .qi-name { font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text); } .qi-meta { font-size: 10px; color: var(--muted); margin-top: 2px; } .qi-status { font-size: 10px; letter-spacing: 1px; text-transform: uppercase; flex-shrink: 0; } .qi-status.pending { color: var(--muted); } .qi-status.scanning { color: var(--warn); } .qi-status.done { color: var(--accent2); } .qi-status.error { color: var(--danger); } .qi-del { background: none; border: none; color: var(--border); cursor: pointer; font-size: 14px; padding: 2px 4px; flex-shrink: 0; } .qi-del:hover { color: var(--danger); } /* ── Batch progress bar ── */ .qi-progress { position: absolute; bottom: 0; left: 0; height: 2px; background: var(--warn); transition: width 0.3s; border-radius: 0 1px 1px 0; } /* ── Batch controls ── */ .batch-controls { padding: 16px 20px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 8px; } .batch-summary { font-size: 11px; color: var(--muted); display: flex; justify-content: space-between; } .batch-summary b { color: var(--text); } /* ── Buttons ── */ .btn { width: 100%; padding: 12px; border-radius: 7px; border: none; font-family: 'Syne', sans-serif; font-weight: 700; font-size: 12px; letter-spacing: 1px; text-transform: uppercase; cursor: pointer; transition: all 0.15s; } .btn-primary { background: var(--accent); color: #0f0f0f; } .btn-primary:hover { background: #d4ff55; } .btn-primary:disabled { opacity: 0.35; cursor: not-allowed; } .btn-secondary { background: var(--surface2); color: var(--text); border: 1px solid var(--border); } .btn-secondary:hover { border-color: var(--muted); } .btn-export { background: transparent; color: var(--accent2); border: 1.5px solid var(--accent2); } .btn-export:hover { background: rgba(66,245,200,0.07); } .btn-danger { background: transparent; color: var(--danger); border: 1.5px solid var(--danger); } .btn-danger:hover { background: rgba(255,95,95,0.07); } .btn-sm { padding: 7px 12px; font-size: 10px; width: auto; } /* ── Right panel ── */ .right-panel { display: flex; flex-direction: column; overflow: hidden; } /* ── Preview panel (right-most column: shows the source PDF/image) ── */ .preview-panel { display: flex; flex-direction: column; border-left: 1px solid var(--border); background: var(--surface); overflow: hidden; min-width: 0; /* allow grid to shrink it cleanly */ } .preview-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; gap: 10px; border-bottom: 1px solid var(--border); flex-shrink: 0; } .preview-title { font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .preview-filename { font-size: 11px; color: var(--text); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .preview-toggle { background: none; border: 1px solid var(--border); color: var(--muted); cursor: pointer; padding: 4px 9px; border-radius: 4px; font-family: inherit; font-size: 14px; line-height: 1; flex-shrink: 0; } .preview-toggle:hover { color: var(--text); border-color: var(--muted); } .preview-body { flex: 1; overflow: hidden; position: relative; background: #1a1a1a; } .preview-body iframe { width: 100%; height: 100%; border: 0; background: #fff; } .preview-body img { width: 100%; height: 100%; object-fit: contain; display: block; } .preview-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 10px; color: var(--muted); font-size: 12px; text-align: center; padding: 20px; } .preview-empty-icon { font-size: 32px; opacity: 0.6; } /* When collapsed, hide everything except a vertical reopen tab. */ main.preview-collapsed .preview-header, main.preview-collapsed .preview-body { display: none; } .preview-collapsed-tab { display: none; /* shown only when .preview-collapsed is set */ width: 36px; height: 100%; align-items: center; justify-content: center; cursor: pointer; user-select: none; color: var(--muted); border-left: 1px solid var(--border); background: var(--surface); font-size: 11px; letter-spacing: 1.5px; writing-mode: vertical-rl; text-orientation: mixed; padding: 12px 0; } .preview-collapsed-tab:hover { color: var(--text); } main.preview-collapsed .preview-collapsed-tab { display: flex; } @media (max-width: 860px) { /* On mobile the panel is a fixed drawer; the collapsed-tab lives as a floating button instead of a column. */ main.preview-collapsed .preview-collapsed-tab { position: fixed; right: 0; top: 50%; transform: translateY(-50%); height: auto; padding: 14px 8px; border-radius: 6px 0 0 6px; border: 1px solid var(--border); border-right: 0; z-index: 25; } } /* ── PDF page strip ── */ .page-strip-wrap { border-bottom: 1px solid var(--border); background: var(--surface); display: none; } .page-strip-header { padding: 10px 24px; display: flex; align-items: center; justify-content: space-between; } .page-strip-label { font-size: 10px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--muted); } .page-strip { display: flex; gap: 8px; padding: 10px 24px; overflow-x: auto; } .page-thumb { flex-shrink: 0; width: 64px; cursor: pointer; border-radius: 5px; overflow: hidden; border: 1.5px solid var(--border); transition: border-color 0.15s; position: relative; } .page-thumb:hover { border-color: var(--muted); } .page-thumb.active { border-color: var(--accent); } .page-thumb.focus-match { border-color: var(--accent2); box-shadow: 0 0 0 2px rgba(66,245,200,0.35) inset; } .page-thumb canvas { width: 100%; display: block; } .page-thumb .pg-num { position: absolute; bottom: 0; left: 0; right: 0; text-align: center; font-size: 9px; background: rgba(0,0,0,0.7); padding: 2px; color: var(--muted); } .page-thumb.active .pg-num { color: var(--accent); } /* ── Result area ── */ .result-area { flex: 1; overflow-y: auto; padding: 28px; } .result-area.empty { display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 14px; color: var(--muted); } .empty-icon { font-size: 44px; opacity: 0.2; } .empty-text { font-size: 12px; text-align: center; line-height: 1.8; } /* ── Result header ── */ .result-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; flex-wrap: gap; gap: 10px; } .result-filename { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 15px; } .result-filename small { font-family: 'DM Mono', monospace; font-size: 11px; font-weight: 400; color: var(--muted); margin-left: 8px; } .result-actions { display: flex; gap: 8px; flex-wrap: wrap; } /* ── Fields ── */ .fields-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 20px; } .field-card { background: var(--surface); border: 1px solid var(--border); border-radius: 7px; padding: 10px 13px; transition: border-color 0.15s; } .field-card:focus-within { border-color: var(--accent); } .field-card.full { grid-column: 1 / -1; } .field-card.highlight { border-color: #3a4a1a; background: #141a08; } .field-card.highlight:focus-within { border-color: var(--accent); } .field-card.validation-good { border-color: #2e7d32; background: rgba(46,125,50,0.12); } .field-card.validation-bad { border-color: #ff5f5f; background: rgba(255,95,95,0.16); } .field-card.validation-bad .field-label, .field-card.validation-bad .field-value { color: #ff9a9a; } .field-card.validation-good .field-label, .field-card.validation-good .field-value { color: #9ff7b2; } .field-label { font-size: 9px; letter-spacing: 1.5px; text-transform: uppercase; color: var(--muted); margin-bottom: 5px; display: flex; align-items: center; justify-content: space-between; gap: 6px; } .field-value { font-size: 13px; color: var(--text); font-family: 'DM Mono', monospace; background: none; border: none; outline: none; width: 100%; } .field-value:focus { color: var(--accent); } .field-card.highlight .field-value { color: var(--accent); font-size: 15px; } /* ── Field confidence dots ── */ /* Tiny coloured dot in the upper-right of each field card, indicating how confident the parser was about the value. Lets the operator triage where to look during review. Empty fields get no dot at all (no signal needed — the placeholder dash already draws the eye). */ .conf-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; cursor: help; opacity: 0.85; } .conf-dot.conf-high { background: #5fd97a; box-shadow: 0 0 6px rgba(95,217,122,0.4); } .conf-dot.conf-med { background: var(--warn); box-shadow: 0 0 6px rgba(245,167,66,0.5); } .conf-dot.conf-low { background: var(--danger); box-shadow: 0 0 8px rgba(255,95,95,0.55); animation: confPulse 1.6s ease-in-out infinite; } @keyframes confPulse { 0%,100% { opacity: 0.7; } 50% { opacity: 1; } } /* ── Custom columns ── */ /* User-defined extra fields (e.g. "Insurance fee", "PO Number") that travel into CSV / JSON exports. Stored in localStorage so they persist across scans and survive page reloads. The remove-X is in the field-label so it sits next to the confidence dot's slot without changing card height. */ .field-card.field-custom { border-style: dashed; } .field-card.field-custom .field-label { color: var(--accent2); } .custom-remove-btn { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 11px; padding: 0 4px; line-height: 1; opacity: 0.6; transition: opacity 0.15s, color 0.15s; } .custom-remove-btn:hover { opacity: 1; color: var(--danger); } /* "+ Add column" tile that lives inside the fields-grid. Uses dashed accent border so it reads as an action, not a field. Notes: - We reset every relevant browser-button default (appearance, outline, box-shadow, font, line-height) because the inherited UA styles caused a hover/active flicker — the UA outline ring would briefly land where :hover stopped, the cursor moved by 1px, hover ended, repeat. - transition is scoped to border-color/color only. `transition: all` was animating things like the UA-default focus ring width on click and contributing to the flicker. */ .add-column-card { grid-column: 1 / -1; display: block; width: 100%; border: 1.5px dashed var(--border); border-radius: 7px; padding: 12px 14px; cursor: pointer; transition: border-color 0.15s ease, color 0.15s ease; background: transparent; color: var(--muted); font-family: 'DM Mono', monospace; font-size: 11px; letter-spacing: 1px; text-transform: uppercase; text-align: center; line-height: 1.4; -webkit-appearance: none; appearance: none; outline: none; box-shadow: none; } .add-column-card:hover { border-color: var(--accent2); color: var(--accent2); } .add-column-card:focus-visible { border-color: var(--accent2); color: var(--accent2); } .add-column-card:active { transform: none; } /* The inline form replaces the add tile while the user fills it in. */ .add-column-form { grid-column: 1 / -1; border: 1.5px dashed var(--accent2); border-radius: 7px; padding: 14px 16px; background: rgba(66,245,200,0.04); display: flex; flex-direction: column; gap: 10px; } .add-column-form .acf-row { display: flex; gap: 10px; align-items: center; } .add-column-form input[type="text"], .add-column-form select { background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: 5px; padding: 8px 10px; font-family: inherit; font-size: 12px; outline: none; } .add-column-form input[type="text"] { flex: 1; min-width: 0; } .add-column-form input[type="text"]:focus, .add-column-form select:focus { border-color: var(--accent2); } .add-column-form .acf-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 4px; } .add-column-form .acf-hint { font-size: 10px; color: var(--muted); line-height: 1.4; } .add-column-form .acf-hint b { color: var(--accent2); font-weight: 500; } /* ── Page OCR nav ── */ .page-ocr-nav { display: flex; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; } .page-pill { background: var(--surface2); border: 1px solid var(--border); border-radius: 4px; padding: 4px 10px; font-size: 10px; cursor: pointer; transition: all 0.15s; letter-spacing: 0.5px; } .page-pill.active { background: var(--accent); color: #0f0f0f; border-color: var(--accent); } .page-pill:hover:not(.active) { border-color: var(--muted); } /* ── Line items ── */ .section-label { font-size: 10px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; } .add-row-btn { background: none; border: 1px solid var(--border); color: var(--muted); border-radius: 4px; padding: 3px 8px; font-size: 10px; cursor: pointer; font-family: inherit; letter-spacing: 1px; } .add-row-btn:hover { border-color: var(--accent); color: var(--accent); } .items-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; font-size: 11px; } .items-table th { text-align: left; padding: 7px 9px; color: var(--muted); font-size: 9px; letter-spacing: 1px; text-transform: uppercase; border-bottom: 1px solid var(--border); font-weight: 400; } .items-table td { padding: 7px 9px; border-bottom: 1px solid var(--border); } .items-table td input { background: none; border: none; outline: none; width: 100%; color: var(--text); font-family: 'DM Mono', monospace; font-size: 11px; } .items-table td input:focus { color: var(--accent); } .items-table tr:hover td { background: var(--surface); } .del-row { background: none; border: none; color: var(--border); cursor: pointer; font-size: 13px; } .del-row:hover { color: var(--danger); } /* ── Raw text ── */ details { margin-top: 12px; border: 1px solid var(--border); border-radius: 7px; overflow: hidden; } details summary { padding: 9px 14px; cursor: pointer; font-size: 10px; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); background: var(--surface); list-style: none; } details summary:hover { color: var(--text); } details[open] summary { border-bottom: 1px solid var(--border); } .raw-text-box { padding: 14px; font-size: 10px; color: var(--muted); white-space: pre-wrap; word-break: break-word; line-height: 1.7; max-height: 180px; overflow-y: auto; background: var(--surface); } /* ── Global scanning overlay ── */ .scan-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.7); z-index: 500; align-items: center; justify-content: center; flex-direction: column; gap: 20px; } .scan-overlay.show { display: flex; } .scan-box { background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 32px 40px; text-align: center; min-width: 320px; } .scan-title { font-family: 'Syne', sans-serif; font-weight: 700; font-size: 16px; margin-bottom: 6px; } .scan-sub { font-size: 11px; color: var(--muted); margin-bottom: 20px; } .scan-progress-bg { height: 4px; background: var(--border); border-radius: 2px; overflow: hidden; margin-bottom: 10px; } .scan-progress-fill { height: 100%; background: var(--accent); border-radius: 2px; transition: width 0.3s; } .scan-status { font-size: 11px; color: var(--muted); } .scan-cancel { background: none; border: 1px solid var(--border); color: var(--muted); border-radius: 6px; padding: 8px 16px; font-family: inherit; font-size: 11px; cursor: pointer; margin-top: 16px; } .scan-cancel:hover { border-color: var(--danger); color: var(--danger); } /* ── Toast ── */ #toast { position: fixed; bottom: 24px; right: 24px; background: var(--surface2); border: 1px solid var(--accent); color: var(--accent); padding: 11px 18px; border-radius: 7px; font-size: 12px; transform: translateY(70px); opacity: 0; transition: all 0.3s; z-index: 999; max-width: 320px; } #toast.show { transform: translateY(0); opacity: 1; } #toast.error { border-color: var(--danger); color: var(--danger); } #toast.warn { border-color: var(--warn); color: var(--warn); } /* ── Batch export bar ── */ .export-bar { background: var(--surface); border-top: 1px solid var(--border); padding: 14px 28px; display: none; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; } .export-bar.show { display: flex; } .export-bar-info { font-size: 12px; color: var(--muted); } .export-bar-info b { color: var(--accent2); } .export-bar-actions { display: flex; gap: 8px; } /* scrollbar */ ::-webkit-scrollbar { width: 5px; height: 5px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
Hybrid PDF + Image + OCR · Self-Learning
Upload Invoice
📂
Drop files here
PDF, PNG, JPG, WEBP, BMP, TIFF
PDF PNG JPG WEBP TIFF
Pages
🧾
Upload an invoice and scan it
Multi-page PDFs and batch mode supported
0 invoices scanned and ready to export
Scanning Invoice
Please wait...
Initializing...