From a8e3e62dbd7052a8a2bd9fa3566fa495b9d03a89 Mon Sep 17 00:00:00 2001 From: rajch_ales Date: Thu, 6 Aug 2026 00:02:25 +0200 Subject: [PATCH] Store selected picture candidates in scrape DB --- agent.md | 1 + public/app.js | 128 ++++++++++- public/app.ts | 151 ++++++++++++- public/styles.css | 2 +- src/db/scrape-image-store.ts | 319 +++++++++++++++++++++++++++ src/server.ts | 7 + src/services/catalog-products.ts | 5 + src/tailwind.css | 17 +- tests/ui/catalog-maker.smoke.spec.ts | 83 +++++++ 9 files changed, 697 insertions(+), 16 deletions(-) create mode 100644 src/db/scrape-image-store.ts diff --git a/agent.md b/agent.md index 980e373..a9dbb24 100644 --- a/agent.md +++ b/agent.md @@ -166,6 +166,7 @@ There are three selectable data sources: - Each source row has its own `Get pictures` action. - Picture candidates must show source, image dimensions and the proposed target assignment. The target assignment is not always color: supported strategies are `color`, `size`, `combination` and `manual`. Start by using manual corrections from the user, then learn reusable rules in Local scrape DB. - Scraped pictures and their metadata belong in Local scrape DB, not browser localStorage. Store the binary image in `scrape_image_blobs` with `sha256`, `perceptual_hash`, mime type, dimensions and size. Store each source/target proposal in `scrape_picture_candidates`. +- Every picture candidate row must have its own save action. Saving one candidate stores only that selected image candidate into Local scrape DB and never saves the whole source result list. - Use `sha256` to remove exact duplicate image files and a perceptual hash to flag visually similar images from different sources or sizes. Exact duplicates may be collapsed automatically; visual duplicates should remain reviewable until the user approves the rule. - Product source cache is cleared when a new product loads. - Hudy picture parsing uses the correct product color variant, checks EANs and extracts large gallery images. diff --git a/public/app.js b/public/app.js index 4888886..e319fd8 100644 --- a/public/app.js +++ b/public/app.js @@ -856,21 +856,62 @@ function renderSourceCandidates(items) { } function renderPictureCandidates(items) { const rows = []; + let position = 0; for (const item of items) { for (const candidate of item.candidates || []) { for (const picture of candidate.pictures || []) { - const row = document.createElement("a"); + position += 1; + const target = buildPictureTargetInfo(); + const row = document.createElement("div"); row.className = "picture-candidate"; - row.href = picture; - row.target = "_blank"; - row.rel = "noreferrer"; + const preview = document.createElement("a"); + preview.href = picture; + preview.target = "_blank"; + preview.rel = "noreferrer"; + preview.className = "picture-candidate-preview"; const image = document.createElement("img"); image.src = picture; image.alt = candidate.rawTitle || item.source?.name || "Product picture"; image.loading = "lazy"; + preview.append(image); + const details = document.createElement("div"); + details.className = "picture-candidate-meta"; const label = document.createElement("span"); - label.textContent = item.source?.name || item.source?.key || "Source"; - row.append(image, label); + label.className = "picture-candidate-title"; + label.textContent = `${item.source?.name || item.source?.key || "Source"} picture #${position}`; + const dimensions = document.createElement("span"); + dimensions.className = "picture-candidate-dimensions"; + dimensions.textContent = "Size: loading..."; + image.addEventListener("load", () => { + dimensions.textContent = image.naturalWidth && image.naturalHeight + ? `Size: ${image.naturalWidth} x ${image.naturalHeight}px` + : "Size: unknown"; + }); + image.addEventListener("error", () => { + dimensions.textContent = "Size: image preview failed"; + }); + const assignment = document.createElement("span"); + assignment.className = "picture-candidate-target"; + assignment.textContent = target.label; + const duplicateHint = document.createElement("span"); + duplicateHint.className = "picture-candidate-duplicate"; + duplicateHint.textContent = "DB: not saved"; + details.append(label, dimensions, assignment, duplicateHint); + const action = document.createElement("button"); + action.type = "button"; + action.className = "source-picture-action picture-candidate-save"; + action.textContent = "Save to DB"; + action.addEventListener("click", () => savePictureCandidateToDb({ + picture, + item, + candidate, + image, + position, + target, + action, + duplicateHint, + })); + row.append(preview, details, action); rows.push(row); } } @@ -884,6 +925,81 @@ function renderPictureCandidates(items) { } sourceList.replaceChildren(...rows); } +function buildPictureTargetInfo() { + const supplierColorGroup = getFirstSupplierColorGroup(); + const firstSupplierRow = supplierColorGroup?.supplierRows?.[0] || currentCombinations[0] || {}; + const color = supplierColorGroup?.color || formatValue(firstSupplierRow.color); + const ean13 = supplierColorGroup?.supplierEan || firstSupplierRow.ean13 || ""; + if (color) { + return { + strategy: "color", + color, + sizeValue: "", + combi: "", + ean13, + idProductCatalog: firstSupplierRow.idProductCatalog || currentProduct?.idProductCatalog || "", + label: `Target: color ${color}${ean13 ? ` / EAN ${ean13}` : ""}`, + }; + } + return { + strategy: "unknown", + color: "", + sizeValue: "", + combi: "", + ean13, + idProductCatalog: firstSupplierRow.idProductCatalog || currentProduct?.idProductCatalog || "", + label: ean13 ? `Target: unknown / EAN ${ean13}` : "Target: needs manual assignment", + }; +} +async function savePictureCandidateToDb({ picture, item, candidate, image, position, target, action, duplicateHint, }) { + action.disabled = true; + action.textContent = "Saving..."; + duplicateHint.textContent = "DB: saving..."; + try { + const payload = { + sourceKey: item.source?.key || "", + sourceName: item.source?.name || "", + sourceType: item.source?.type || "", + imageUrl: picture, + pageUrl: candidate.url || item.searchUrl || "", + sourcePosition: position, + manufacturerId: manufacturerSelect.value, + idProductCatalog: currentProduct?.idProductCatalog || target.idProductCatalog || "", + idProduct: currentProduct?.idProduct || "", + supplierReference: currentProduct?.supplierReference || "", + ean13: target.ean13 || "", + color: target.color || "", + sizeValue: target.sizeValue || "", + combi: target.combi || "", + targetStrategy: target.strategy, + targetColor: target.color || "", + targetSize: target.sizeValue || "", + targetCombi: target.combi || "", + targetEan13: target.ean13 || "", + targetIdProductCatalog: target.idProductCatalog || currentProduct?.idProductCatalog || "", + matchConfidence: target.strategy === "color" ? 70 : 0, + width: image.naturalWidth || "", + height: image.naturalHeight || "", + }; + const response = await fetch("/api/catalog-maker/save-picture", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + }); + const data = await response.json(); + if (!response.ok) + throw new Error(data.message || "Picture save failed."); + action.textContent = "Saved"; + duplicateHint.textContent = data.duplicateStatus === "exact_duplicate" + ? `DB: duplicate of #${data.duplicateOfCandidateId || "known"}` + : `DB: saved #${data.candidateId}`; + } + catch (error) { + action.disabled = false; + action.textContent = "Save to DB"; + duplicateHint.textContent = `DB: ${error.message || "save failed"}`; + } +} function collectPictureUrls(items) { return items.flatMap((item) => (item.candidates || []).flatMap((candidate) => candidate.pictures || [])).filter((url, index, all) => all.indexOf(url) === index); } diff --git a/public/app.ts b/public/app.ts index 0a71f9a..d910e3f 100644 --- a/public/app.ts +++ b/public/app.ts @@ -928,23 +928,74 @@ function renderSourceCandidates(items) { function renderPictureCandidates(items) { const rows = []; + let position = 0; for (const item of items) { for (const candidate of item.candidates || []) { for (const picture of candidate.pictures || []) { - const row = document.createElement("a"); + position += 1; + const target = buildPictureTargetInfo(); + const row = document.createElement("div"); row.className = "picture-candidate"; - row.href = picture; - row.target = "_blank"; - row.rel = "noreferrer"; + + const preview = document.createElement("a"); + preview.href = picture; + preview.target = "_blank"; + preview.rel = "noreferrer"; + preview.className = "picture-candidate-preview"; const image = document.createElement("img"); image.src = picture; image.alt = candidate.rawTitle || item.source?.name || "Product picture"; image.loading = "lazy"; + preview.append(image); + + const details = document.createElement("div"); + details.className = "picture-candidate-meta"; const label = document.createElement("span"); - label.textContent = item.source?.name || item.source?.key || "Source"; - row.append(image, label); + label.className = "picture-candidate-title"; + label.textContent = `${item.source?.name || item.source?.key || "Source"} picture #${position}`; + + const dimensions = document.createElement("span"); + dimensions.className = "picture-candidate-dimensions"; + dimensions.textContent = "Size: loading..."; + image.addEventListener("load", () => { + dimensions.textContent = image.naturalWidth && image.naturalHeight + ? `Size: ${image.naturalWidth} x ${image.naturalHeight}px` + : "Size: unknown"; + }); + image.addEventListener("error", () => { + dimensions.textContent = "Size: image preview failed"; + }); + + const assignment = document.createElement("span"); + assignment.className = "picture-candidate-target"; + assignment.textContent = target.label; + + const duplicateHint = document.createElement("span"); + duplicateHint.className = "picture-candidate-duplicate"; + duplicateHint.textContent = "DB: not saved"; + + details.append(label, dimensions, assignment, duplicateHint); + + const action = document.createElement("button"); + action.type = "button"; + action.className = "source-picture-action picture-candidate-save"; + action.textContent = "Save to DB"; + action.addEventListener("click", () => + savePictureCandidateToDb({ + picture, + item, + candidate, + image, + position, + target, + action, + duplicateHint, + }), + ); + + row.append(preview, details, action); rows.push(row); } } @@ -960,6 +1011,94 @@ function renderPictureCandidates(items) { sourceList.replaceChildren(...rows); } +function buildPictureTargetInfo() { + const supplierColorGroup = getFirstSupplierColorGroup(); + const firstSupplierRow = supplierColorGroup?.supplierRows?.[0] || currentCombinations[0] || {}; + const color = supplierColorGroup?.color || formatValue(firstSupplierRow.color); + const ean13 = supplierColorGroup?.supplierEan || firstSupplierRow.ean13 || ""; + + if (color) { + return { + strategy: "color", + color, + sizeValue: "", + combi: "", + ean13, + idProductCatalog: firstSupplierRow.idProductCatalog || currentProduct?.idProductCatalog || "", + label: `Target: color ${color}${ean13 ? ` / EAN ${ean13}` : ""}`, + }; + } + + return { + strategy: "unknown", + color: "", + sizeValue: "", + combi: "", + ean13, + idProductCatalog: firstSupplierRow.idProductCatalog || currentProduct?.idProductCatalog || "", + label: ean13 ? `Target: unknown / EAN ${ean13}` : "Target: needs manual assignment", + }; +} + +async function savePictureCandidateToDb({ + picture, + item, + candidate, + image, + position, + target, + action, + duplicateHint, +}) { + action.disabled = true; + action.textContent = "Saving..."; + duplicateHint.textContent = "DB: saving..."; + + try { + const payload = { + sourceKey: item.source?.key || "", + sourceName: item.source?.name || "", + sourceType: item.source?.type || "", + imageUrl: picture, + pageUrl: candidate.url || item.searchUrl || "", + sourcePosition: position, + manufacturerId: manufacturerSelect.value, + idProductCatalog: currentProduct?.idProductCatalog || target.idProductCatalog || "", + idProduct: currentProduct?.idProduct || "", + supplierReference: currentProduct?.supplierReference || "", + ean13: target.ean13 || "", + color: target.color || "", + sizeValue: target.sizeValue || "", + combi: target.combi || "", + targetStrategy: target.strategy, + targetColor: target.color || "", + targetSize: target.sizeValue || "", + targetCombi: target.combi || "", + targetEan13: target.ean13 || "", + targetIdProductCatalog: target.idProductCatalog || currentProduct?.idProductCatalog || "", + matchConfidence: target.strategy === "color" ? 70 : 0, + width: image.naturalWidth || "", + height: image.naturalHeight || "", + }; + const response = await fetch("/api/catalog-maker/save-picture", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(payload), + }); + const data = await response.json(); + if (!response.ok) throw new Error(data.message || "Picture save failed."); + + action.textContent = "Saved"; + duplicateHint.textContent = data.duplicateStatus === "exact_duplicate" + ? `DB: duplicate of #${data.duplicateOfCandidateId || "known"}` + : `DB: saved #${data.candidateId}`; + } catch (error) { + action.disabled = false; + action.textContent = "Save to DB"; + duplicateHint.textContent = `DB: ${error.message || "save failed"}`; + } +} + function collectPictureUrls(items) { return items.flatMap((item) => (item.candidates || []).flatMap((candidate) => candidate.pictures || []), diff --git a/public/styles.css b/public/styles.css index 1576606..fe57269 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-leading:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-300:oklch(80.8% .114 19.571);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-lime-700:oklch(53.2% .157 131.589);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-700:oklch(50.8% .118 165.612);--color-teal-50:oklch(98.4% .014 180.72);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-2xl:42rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--radius-lg:.5rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-catalog-surface:#f4f6f8;--color-catalog-panel-soft:#2d333b;--color-catalog-accent:#7ee34d;--color-catalog-accent-text:#10220b;--color-catalog-focus:#2f6fed}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:before,:after{box-sizing:border-box}html{color-scheme:light}html.dark{color-scheme:dark}body{min-height:100vh;font-family:var(--font-sans);color:#15181c;background:var(--color-catalog-surface);margin:0}button,input,select{font:inherit}}@layer components{.catalog-screen{background-color:var(--color-catalog-surface);min-height:100vh;padding-left:calc(var(--spacing) * 72);--secondary-track:minmax(240px,292px);--workspace-track:minmax(0,1fr);grid-template-columns:var(--secondary-track) var(--workspace-track);display:grid;position:relative}.catalog-screen.sidebar-collapsed{padding-left:calc(var(--spacing) * 11)}.catalog-screen.secondary-collapsed{--secondary-track:44px}.catalog-screen.workspace-collapsed{--workspace-track:44px}.sidebar-drawer{z-index:40;min-height:100vh;width:calc(var(--spacing) * 72);gap:calc(var(--spacing) * 1.5);background-color:var(--color-slate-900);padding:calc(var(--spacing) * 1.5);color:var(--color-slate-200);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-out);transition-duration:.2s;transition-timing-function:var(--ease-out);inset-block:0;flex-direction:column;font-size:11px;display:flex;position:fixed;left:0;overflow-y:auto}.panel-header,.sidebar-drawer-header,.column-header{box-sizing:border-box;height:calc(var(--spacing) * 7);min-height:calc(var(--spacing) * 7);justify-content:space-between;align-items:center;gap:var(--spacing);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-900);padding-inline:var(--spacing);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);color:var(--color-slate-300);text-transform:uppercase;display:flex}.sidebar-drawer-header{margin-inline:calc(var(--spacing) * -1.5);margin-top:calc(var(--spacing) * -1.5);margin-bottom:calc(var(--spacing) * 1.5)}.sidebar-drawer-title{padding-inline:var(--spacing);color:var(--color-slate-300)}.sidebar-close-button,.sidebar-open-button{height:calc(var(--spacing) * 7);width:calc(var(--spacing) * 7);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-200);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;justify-content:center;align-items:center;display:inline-flex}@media (hover:hover){:is(.sidebar-close-button,.sidebar-open-button):hover{border-color:var(--color-slate-500);background-color:var(--color-slate-700)}}.sidebar-close-button .sidebar-icon,.sidebar-open-button .sidebar-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4)}.panel-control-button{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-400);background-color:var(--color-white);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1;color:var(--color-slate-700);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;flex-shrink:0;justify-content:center;align-items:center;line-height:1;display:inline-flex}@media (hover:hover){.panel-control-button:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-200)}}.panel-control-button:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.panel-control-button:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.panel-control-button:focus{--tw-outline-style:none;outline-style:none}.sidebar-open-button{top:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2);z-index:30;position:fixed}.sidebar-overlay{z-index:30;background-color:#02061880;position:fixed;inset:0}@supports (color:color-mix(in lab, red, red)){.sidebar-overlay{background-color:color-mix(in oklab, var(--color-slate-950) 50%, transparent)}}.sidebar-overlay[hidden]{display:none}.sidebar-drawer[aria-hidden=true]{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.sidebar-section{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-700);padding-top:var(--spacing)}.drawer-trigger{min-height:calc(var(--spacing) * 8);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);width:100%;padding-inline:calc(var(--spacing) * 8);text-align:center;--tw-font-weight:var(--font-weight-semibold);font-size:11px;font-weight:var(--font-weight-semibold);color:var(--color-slate-100);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;justify-content:center;align-items:center;display:flex;position:relative}@media (hover:hover){.drawer-trigger:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-700)}}.drawer-trigger .sidebar-icon{position:static}.drawer-trigger>span:not(.drawer-chevron){text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.drawer-trigger .drawer-chevron{right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);--tw-leading:1;color:var(--color-slate-300);border-bottom-width:2px;font-size:0;line-height:1;display:inline-flex;position:absolute;rotate:45deg}.drawer-trigger[aria-expanded=true] .drawer-chevron{rotate:225deg}.parser-drawer[hidden]{display:none}.sidebar-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1;color:var(--color-slate-300);flex-shrink:0;justify-content:center;align-items:center;line-height:1;display:inline-flex}.action-button{justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);display:flex}.action-button>.sidebar-icon{flex-shrink:0}.action-button>span{text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.action-button .sidebar-icon{color:var(--color-slate-400)}.sidebar-footer{gap:var(--spacing);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-700);padding-top:calc(var(--spacing) * 2);margin-top:auto;display:grid}.app-status{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);padding:var(--spacing);--tw-leading:var(--leading-tight);font-size:10px;line-height:var(--leading-tight);color:var(--color-slate-100);border-radius:.25rem}.database-status-content{justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);text-align:center;display:flex}.database-state-dot{height:calc(var(--spacing) * 2.5);width:calc(var(--spacing) * 2.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-500);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.database-state-dot.is-ready{border-color:var(--color-emerald-300);background-color:var(--color-emerald-500)}.database-state-dot.is-pending{border-color:var(--color-amber-300);background-color:var(--color-amber-400)}.database-state-dot.is-error{border-color:var(--color-red-300);background-color:var(--color-red-500)}.safe-mode,#modePill{background-color:var(--color-slate-100);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);color:var(--color-slate-800);border-radius:.25rem;display:inline-block}#databaseStatus.database-error{background-color:var(--color-red-100);color:var(--color-red-700)}.record-id{height:calc(var(--spacing) * 10);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-lime-700);background-color:var(--color-catalog-accent);--tw-leading:1;--tw-font-weight:var(--font-weight-bold);font-size:26px;line-height:1;font-weight:var(--font-weight-bold);color:var(--color-catalog-accent-text);border-radius:.25rem;justify-content:center;align-items:center;display:flex}.arrow-row{gap:var(--spacing);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.arrow-button,.action-button,.input-like{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);text-align:center;width:100%;color:var(--color-slate-200);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem}.arrow-button,.action-button{min-height:calc(var(--spacing) * 7);padding-inline:var(--spacing);font-size:11px}.arrow-button{color:var(--color-slate-100);position:relative}.arrow-button:before,.column-toggle:before{content:"";height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);border-bottom-width:2px;display:block}.arrow-button:before{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);border-bottom-width:2px;position:absolute;top:50%;left:50%}.arrow-button-prev:before{rotate:135deg}.arrow-button-next:before{rotate:-45deg}.parser-actions{gap:var(--spacing);display:grid}.parser-actions .action-button{min-height:calc(var(--spacing) * 7)}.action-button.primary{min-height:calc(var(--spacing) * 8);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.arrow-button:hover:not(:disabled),.action-button:hover:not(:disabled){border-color:var(--color-slate-500);background-color:var(--color-slate-700);color:var(--color-white)}.arrow-button:disabled,.action-button:disabled{cursor:not-allowed;opacity:.5}.input-like{height:calc(var(--spacing) * 7);padding-inline:var(--spacing);font-size:11px}.select-control{width:100%;position:relative}.select-display{pointer-events:none;z-index:30;justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);text-align:center;color:var(--color-slate-200);display:flex;position:absolute;inset:0}.select-display span{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:calc(100% - 3rem);overflow:hidden}.language-native-select{z-index:20;cursor:pointer;appearance:none;padding-right:calc(var(--spacing) * 7);padding-left:calc(var(--spacing) * 7);caret-color:#0000;background-color:#0000;position:relative;color:#0000!important}.language-native-select:focus{color:#0000!important}.select-chevron{pointer-events:none;top:50%;right:calc(var(--spacing) * 3);z-index:30;height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);color:var(--color-slate-200);border-bottom-width:2px;position:absolute;rotate:45deg}.native-manufacturer-host,.native-manufacturer-select{display:none}.manufacturer-picker{position:relative}.manufacturer-trigger{height:calc(var(--spacing) * 7);justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);width:100%;padding-inline:calc(var(--spacing) * 9);text-align:center;color:var(--color-slate-200);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:.25rem;font-size:11px;display:flex;position:relative}.manufacturer-trigger>span:not(.select-chevron){text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.manufacturer-trigger:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-700)}.manufacturer-trigger[aria-expanded=true]{border-color:var(--color-catalog-focus);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.manufacturer-trigger[aria-expanded=true]{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.manufacturer-trigger .select-chevron{top:50%;right:calc(var(--spacing) * 3);--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y);margin-left:0}.manufacturer-menu{z-index:50;margin-top:var(--spacing);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);padding:var(--spacing);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:.25rem;position:absolute;top:100%;left:0;right:0}.manufacturer-menu[hidden]{display:none}.manufacturer-search{margin-bottom:var(--spacing);height:calc(var(--spacing) * 7);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);width:100%;padding-inline:calc(var(--spacing) * 2);color:var(--color-slate-800);--tw-outline-style:none;border-radius:.25rem;outline-style:none;font-size:11px}.manufacturer-search:focus{border-color:var(--color-catalog-focus);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.manufacturer-search:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.manufacturer-options{max-height:calc(var(--spacing) * 60);overflow-y:auto}.manufacturer-option{min-height:calc(var(--spacing) * 7);text-overflow:ellipsis;white-space:nowrap;width:100%;padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);text-align:left;color:var(--color-slate-700);border-radius:.25rem;font-size:11px;display:block;overflow:hidden}@media (hover:hover){.manufacturer-option:hover{background-color:var(--color-slate-100)}}.manufacturer-option[aria-selected=true]{background-color:var(--color-slate-200);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.manufacturer-empty{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 2);color:var(--color-slate-500);font-size:11px}#scanInput{background-color:var(--color-slate-700)}#scanInput::placeholder{color:var(--color-slate-400)}.input-like:focus{border-color:var(--color-catalog-focus);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.input-like:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.input-like:focus{--tw-outline-style:none;outline-style:none}.picture-slot{aspect-ratio:1;border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:1px;border-color:var(--color-slate-500);background-color:var(--color-catalog-panel-soft);width:100%;padding:var(--spacing);text-align:center;color:var(--color-slate-200);border-radius:.25rem;font-size:11px;overflow:hidden}.picture-slot img{object-fit:contain;width:100%;height:100%}.mapping-box{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-500);background-color:var(--color-catalog-panel-soft);border-radius:.25rem;position:relative}.sidebar-mapping-box{border-color:var(--color-slate-700);background-color:var(--color-slate-900);overflow:visible}.mapping-details{position:relative}.mapping-summary{min-height:calc(var(--spacing) * 8);background-color:var(--color-slate-800);padding-inline:calc(var(--spacing) * 9);text-align:center;--tw-font-weight:var(--font-weight-semibold);font-size:11px;font-weight:var(--font-weight-semibold);color:var(--color-slate-100);border-radius:.25rem;justify-content:center;align-items:center;display:flex;position:relative}.mapping-label{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);max-width:calc(100% - 4.5rem);translate:var(--tw-translate-x) var(--tw-translate-y);justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);text-align:center;display:inline-flex;position:absolute;top:50%;left:50%}.mapping-title{text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.mapping-state-dot{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-500);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.mapping-state-dot.is-ready{border-color:var(--color-emerald-300);background-color:var(--color-emerald-500)}.mapping-state-dot.is-warning{border-color:var(--color-amber-300);background-color:var(--color-amber-400)}.mapping-state-dot.is-missing{border-color:var(--color-red-300);background-color:var(--color-red-500)}.mapping-state-dot.is-loading{border-color:var(--color-slate-300);background-color:var(--color-slate-400)}.mapping-panel{z-index:50;border-style:var(--tw-border-style);border-width:1px;border-top-style:var(--tw-border-style);border-top-width:0;border-color:var(--color-slate-700);background-color:#020618f2;position:absolute;top:100%;left:0;right:0}@supports (color:color-mix(in lab, red, red)){.mapping-panel{background-color:color-mix(in oklab, var(--color-slate-950) 95%, transparent)}}.mapping-panel{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.mapping-detail-status{text-overflow:ellipsis;white-space:nowrap;padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);text-align:center;--tw-font-weight:var(--font-weight-normal);font-size:10px;font-weight:var(--font-weight-normal);color:var(--color-slate-300);overflow:hidden}.mapping-summary .mapping-toggle{top:50%;right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y);justify-content:center;align-items:center;display:flex;position:absolute}.mapping-details summary{cursor:pointer;list-style-type:none}.mapping-details summary::-webkit-details-marker{display:none}.mapping-toggle:before{content:"";height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-slate-100);display:block;rotate:45deg}.mapping-list{gap:calc(var(--spacing) * .5);padding-inline:var(--spacing);padding-bottom:var(--spacing);display:grid}.mapping-source-row{align-items:center;gap:var(--spacing);background-color:var(--color-slate-100);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);color:var(--color-slate-800);border-radius:.25rem;grid-template-columns:minmax(0,1fr) auto;font-size:10px;display:grid}.mapping-source-row.is-disabled{opacity:.5}.mapping-source-name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mapping-source-status{padding-inline:var(--spacing);--tw-font-weight:var(--font-weight-semibold);font-size:9px;font-weight:var(--font-weight-semibold);border-radius:.25rem}.mapping-source-status.is-ok{background-color:var(--color-emerald-100);color:var(--color-emerald-700)}.mapping-source-status.is-bad{background-color:var(--color-red-100);color:var(--color-red-700)}.mapping-source-status.is-muted{background-color:var(--color-slate-200);color:var(--color-slate-600)}.secondary-panel{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-slate-400);background-color:var(--color-slate-300);min-width:0}.column-header{min-width:0}.column-toggle{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-400);background-color:var(--color-white);--tw-leading:1;color:var(--color-slate-700);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;flex-shrink:0;justify-content:center;align-items:center;font-size:0;line-height:1;display:inline-flex}@media (hover:hover){.column-toggle:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-200)}}.column-toggle:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.column-toggle:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.column-toggle:focus{--tw-outline-style:none;outline-style:none}#secondaryPanelToggle[aria-label="Collapse product details"]:before,#mainWorkspaceToggle[aria-label="Collapse main workspace"]:before{transform:rotate(135deg)}#secondaryPanelToggle[aria-label="Expand product details"]:before,#mainWorkspaceToggle[aria-label="Expand main workspace"]:before{transform:rotate(-45deg)}.secondary-panel .field-grid{overflow:hidden}.secondary-collapsed .secondary-panel .column-header{height:100%;padding-block:var(--spacing);writing-mode:vertical-rl;flex-direction:column;justify-content:flex-start}.secondary-collapsed .secondary-panel .column-header .column-toggle,.workspace-collapsed .main-workspace .workspace-header .column-toggle{writing-mode:horizontal-tb}.secondary-collapsed .secondary-panel .field-grid{display:none}.workspace-collapsed .main-workspace .workspace-header{height:100%;padding-block:var(--spacing);writing-mode:vertical-rl;flex-direction:column;justify-content:flex-start}.workspace-collapsed .main-workspace .workspace-header span,.workspace-collapsed .main-workspace .table-wrap{display:none}.field-grid{grid-template-columns:132px minmax(0,168px);align-content:flex-start;display:grid}.field-label,.field-value{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);min-height:22px;padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);--tw-leading:var(--leading-tight);font-size:11px;line-height:var(--leading-tight)}.field-label{background-color:var(--color-slate-200);color:var(--color-slate-800)}.field-value{background-color:var(--color-teal-50);text-align:right;color:var(--color-slate-900)}.field-value[data-field=manufacturer_name]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.main-workspace{background-color:var(--color-catalog-surface);flex-direction:column;min-width:0;max-height:100vh;display:flex;overflow:hidden}.data-panel{background-color:var(--color-catalog-surface);min-width:0;overflow:hidden}.table-wrap{scrollbar-gutter:stable;flex:1;min-height:0;overflow:auto}.variant-grid{min-width:0;min-width:var(--variant-min-width,820px);font-size:11px}.variant-header,.variant-row{grid-template-columns:var(--variant-columns);display:grid}.variant-header{z-index:10;position:sticky;top:0}.variant-header-cell{border-right-style:var(--tw-border-style);border-right-width:1px;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-200);height:22px;padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--color-slate-800)}.variant-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);min-height:22px}.variant-row:nth-child(2n){background-color:var(--color-slate-100)}.variant-row:hover{background-color:var(--color-slate-200)}.variant-cell{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-slate-300);min-width:0;padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);text-align:center;white-space:nowrap;color:var(--color-slate-800)}.variant-cell[data-label=""]{padding:0}.empty-row{padding-block:calc(var(--spacing) * 4);text-align:center;color:var(--color-slate-500)}.variant-cell.problem-ean{background-color:var(--color-red-600);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-white)}.modal-backdrop{z-index:50;background-color:#0f172b80;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}@supports (color:color-mix(in lab, red, red)){.modal-backdrop{background-color:color-mix(in oklab, var(--color-slate-900) 50%, transparent)}}.modal-backdrop{padding:calc(var(--spacing) * 4)}.modal-backdrop[hidden]{display:none}.suggested-dialog,.source-dialog,.settings-dialog{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-50);--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);width:100%;max-height:90vh;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);overflow:hidden}.source-dialog-actions{align-items:center;gap:calc(var(--spacing) * 1.5);display:flex}.suggested-dialog{max-width:var(--container-2xl)}.source-dialog{max-width:var(--container-3xl)}.settings-dialog{max-width:var(--container-md)}.suggested-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);justify-content:space-between;align-items:center;display:flex}.suggested-header h2{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-800);margin:0}.modal-close,.settings-test-button,.settings-inline-button{justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);color:var(--color-slate-800);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:.25rem;font-size:11px;display:inline-flex}@media (hover:hover){:is(.modal-close,.settings-test-button,.settings-inline-button):hover{background-color:var(--color-slate-100)}}.modal-close .sidebar-icon,.settings-test-button .sidebar-icon,.settings-inline-button .sidebar-icon{height:calc(var(--spacing) * 3.5);width:calc(var(--spacing) * 3.5);color:var(--color-slate-500)}.suggested-list,.source-list,.settings-content{max-height:calc(90vh - 48px);padding:calc(var(--spacing) * 2);overflow-y:auto}.settings-content{gap:calc(var(--spacing) * 2);display:grid}.settings-group{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);padding:calc(var(--spacing) * 2);border-radius:.25rem;margin:0}.settings-group legend{padding-inline:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-700)}.settings-option{align-items:center;gap:var(--spacing);padding-block:calc(var(--spacing) * .5);color:var(--color-slate-700);font-size:11px;display:flex}.settings-option .settings-inline-button{padding-block:calc(var(--spacing) * .5);margin-left:auto;font-size:10px}.settings-tool-actions{align-items:center;gap:var(--spacing);margin-left:auto;display:flex}.settings-tool-actions .settings-inline-button{margin-left:0}.settings-option input{accent-color:var(--color-catalog-focus)}.settings-option input:disabled+span{color:var(--color-slate-400)}.local-db-fields{gap:var(--spacing);display:grid}.local-db-fields[hidden]{display:none}.settings-field{align-items:center;gap:calc(var(--spacing) * 2);color:var(--color-slate-700);grid-template-columns:72px minmax(0,1fr);font-size:11px;display:grid}.settings-field input{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);min-width:0;padding-inline:var(--spacing);padding-block:var(--spacing);border-radius:.25rem;font-size:11px}.settings-select{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);min-width:0;padding-inline:var(--spacing);padding-block:var(--spacing);color:var(--color-slate-800);border-radius:.25rem;font-size:11px}.settings-note,.settings-permissions{--tw-leading:var(--leading-tight);font-size:10px;line-height:var(--leading-tight);color:var(--color-slate-500);margin:0}.settings-permissions{gap:calc(var(--spacing) * .5);display:grid}.adminer-login-hint{margin-top:var(--spacing);gap:calc(var(--spacing) * .5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-100);padding:calc(var(--spacing) * 1.5);--tw-leading:var(--leading-tight);font-size:10px;line-height:var(--leading-tight);color:var(--color-slate-600);border-radius:.25rem;display:grid}.adminer-login-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);display:flex}.adminer-login-row strong{text-overflow:ellipsis;white-space:nowrap;text-align:right;--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-800);overflow:hidden}.connection-ok{color:var(--color-emerald-700)}.connection-error{color:var(--color-red-700)}.suggested-row{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:var(--spacing);padding-block:var(--spacing);grid-template-columns:40px minmax(0,1fr) auto;font-size:11px;display:grid}.suggested-row:nth-child(2n){background-color:var(--color-slate-100)}.suggested-row img{height:calc(var(--spacing) * 9);width:calc(var(--spacing) * 9);object-fit:contain}.suggested-id,.suggested-active{color:var(--color-slate-500)}.suggested-name,.suggested-reference{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.suggested-pick,.source-row-action,.source-picture-action{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 1.5);padding-block:calc(var(--spacing) * .5);border-radius:.25rem;font-size:10px}@media (hover:hover){:is(.suggested-pick,.source-row-action,.source-picture-action):hover{background-color:var(--color-slate-100)}}.source-section{margin-bottom:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);border-radius:.25rem;overflow:hidden}.source-section-header,.source-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);font-size:11px;display:flex}.source-section-header{background-color:var(--color-slate-200);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.source-section-actions{align-items:center;gap:var(--spacing);display:flex}.source-row{background-color:var(--color-white)}@media (hover:hover){.source-row:hover{background-color:var(--color-slate-100)}}.source-row-title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.source-status,.source-row-action{padding-inline:var(--spacing);border-radius:.25rem;flex-shrink:0;font-size:10px}.source-status.is-ok{background-color:var(--color-emerald-100);color:var(--color-emerald-700)}.source-status.is-muted{background-color:var(--color-slate-200);color:var(--color-slate-600)}.picture-candidate{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding:var(--spacing);grid-template-columns:72px minmax(0,1fr);font-size:10px;display:grid}.picture-candidate img{height:calc(var(--spacing) * 16);object-fit:contain;width:72px}.picture-candidate span{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}@media (max-width:980px){.catalog-screen{flex-direction:column;padding-left:0;display:flex}.catalog-screen.sidebar-collapsed{padding-left:0}.sidebar-drawer{width:min(18rem,100vw - 1rem);min-height:0}.sidebar-drawer:not([aria-hidden=true]){max-height:100vh}.sidebar-drawer[aria-hidden=true]{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.sidebar-open-button{top:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2)}.secondary-panel{border-right-style:var(--tw-border-style);border-right-width:0;order:1;padding-top:0}.main-workspace{order:2}.table-wrap{max-height:70vh}.column-header{height:calc(var(--spacing) * 7);writing-mode:horizontal-tb}.secondary-collapsed .secondary-panel .column-header,.workspace-collapsed .main-workspace .workspace-header{height:calc(var(--spacing) * 7);writing-mode:horizontal-tb;flex-direction:row;padding-block:0}.secondary-collapsed .secondary-panel .column-header span,.workspace-collapsed .main-workspace .workspace-header span{display:inline}.workspace-collapsed .main-workspace .table-wrap{display:none}.variant-grid{min-width:0}.variant-header{display:none}.variant-row{column-gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);padding:calc(var(--spacing) * 2);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.variant-cell{min-height:calc(var(--spacing) * 7);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:var(--spacing);padding-block:var(--spacing);text-align:right;white-space:normal;display:flex}.variant-cell:before{content:attr(data-label);text-align:left;--tw-font-weight:var(--font-weight-medium);font-size:10px;font-weight:var(--font-weight-medium);color:var(--color-slate-500);flex-shrink:0}.variant-cell[data-label=""]:before{content:none}.variant-cell[data-label=""]{grid-column:span 2/span 2}.variant-message-row{display:block}html.dark .variant-row,html.dark .variant-cell{border-color:var(--color-slate-700)}}@media (min-width:981px){.sidebar-overlay{display:none}}html:not(.dark) .control-panel{background-color:var(--color-slate-100);color:var(--color-slate-700)}html:not(.dark) .sidebar-drawer-header,html:not(.dark) .sidebar-footer{border-color:var(--color-slate-300)}html:not(.dark) .panel-header,html:not(.dark) .sidebar-drawer-header,html:not(.dark) .column-header{border-color:var(--color-slate-300);background-color:var(--color-slate-100);color:var(--color-slate-700)}html:not(.dark) .sidebar-drawer-title,html:not(.dark) .drawer-trigger .drawer-chevron,html:not(.dark) .select-chevron,html:not(.dark) .select-display,html:not(.dark) .manufacturer-trigger .sidebar-icon{color:var(--color-slate-700)}html:not(.dark) .sidebar-close-button,html:not(.dark) .sidebar-open-button,html:not(.dark) .drawer-trigger,html:not(.dark) .arrow-button,html:not(.dark) .action-button,html:not(.dark) .input-like{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .panel-control-button{border-color:var(--color-slate-400);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .sidebar-close-button:hover,html:not(.dark) .sidebar-open-button:hover,html:not(.dark) .drawer-trigger:hover,html:not(.dark) .arrow-button:hover:not(:disabled),html:not(.dark) .action-button:hover:not(:disabled){border-color:var(--color-slate-400);background-color:var(--color-slate-200);color:var(--color-slate-900)}html:not(.dark) .arrow-button:before{color:var(--color-slate-700)}html:not(.dark) #scanInput{background-color:var(--color-slate-200)}html:not(.dark) #scanInput::placeholder{color:var(--color-slate-500)}html:not(.dark) .picture-slot,html:not(.dark) .mapping-box{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-600)}html:not(.dark) .mapping-toggle:before{border-color:var(--color-slate-700)}html:not(.dark) .app-status{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .sidebar-mapping-box{border-color:var(--color-slate-300);background-color:var(--color-slate-100)}html:not(.dark) .mapping-summary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .mapping-panel{border-color:var(--color-slate-300);background-color:var(--color-white)}html:not(.dark) .mapping-detail-status{color:var(--color-slate-600)}html:not(.dark) .manufacturer-trigger{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .manufacturer-trigger:hover{border-color:var(--color-slate-400);background-color:var(--color-slate-200)}html.contrast-light-high:not(.dark) .control-panel{background-color:var(--color-slate-200);color:var(--color-slate-900)}html.contrast-light-high:not(.dark) .action-button,html.contrast-light-high:not(.dark) .arrow-button,html.contrast-light-high:not(.dark) .input-like,html.contrast-light-high:not(.dark) .manufacturer-trigger{border-color:var(--color-slate-500);color:var(--color-slate-900)}html.contrast-light-high:not(.dark) .sidebar-icon{color:var(--color-slate-700)}html.dark body{background-color:var(--color-slate-950);color:var(--color-slate-100)}html.dark .control-panel{background-color:var(--color-slate-950)}html.dark .secondary-panel{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}html.dark .data-panel{background-color:var(--color-slate-950)}html.dark .field-label{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-200)}html.dark .field-value{border-color:var(--color-slate-700);background-color:var(--color-slate-900);color:var(--color-slate-100)}html.dark .variant-header-cell{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .variant-row{border-color:var(--color-slate-700)}html.dark .variant-row:nth-child(2n){background-color:var(--color-slate-800)}html.dark .variant-row:hover{background-color:var(--color-slate-700)}html.dark .variant-cell{border-color:var(--color-slate-700);color:var(--color-slate-200)}html.dark .arrow-button,html.dark .action-button,html.dark .input-like{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .arrow-button:hover:not(:disabled),html.dark .action-button:hover:not(:disabled){border-color:var(--color-slate-400);background-color:var(--color-slate-700)}html.dark #scanInput{background-color:var(--color-slate-700)}html.dark #scanInput::placeholder{color:var(--color-slate-400)}html.dark .manufacturer-menu{border-color:var(--color-slate-700);background-color:var(--color-slate-900)}html.dark .manufacturer-search{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .manufacturer-option{color:var(--color-slate-200)}@media (hover:hover){html.dark .manufacturer-option:hover{background-color:var(--color-slate-800)}}html.dark .manufacturer-option[aria-selected=true]{background-color:var(--color-slate-700);color:var(--color-white)}html.dark .column-header{border-color:var(--color-slate-700);color:var(--color-slate-300)}html.dark .panel-control-button,html.dark .column-toggle{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}@media (hover:hover){:is(html.dark .panel-control-button,html.dark .column-toggle):hover{border-color:var(--color-slate-400);background-color:var(--color-slate-700)}}html.contrast-dark-high.dark .control-panel{background-color:var(--color-slate-950);color:var(--color-white)}html.contrast-dark-high.dark .action-button,html.contrast-dark-high.dark .arrow-button,html.contrast-dark-high.dark .input-like,html.contrast-dark-high.dark .manufacturer-trigger{border-color:var(--color-slate-500);background-color:var(--color-slate-700);color:var(--color-white)}html.contrast-dark-high.dark .sidebar-icon{color:var(--color-slate-100)}html.dark .settings-dialog,html.dark .suggested-dialog,html.dark .source-dialog{border-color:var(--color-slate-700);background-color:var(--color-slate-900)}html.dark .suggested-header{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}html.dark .suggested-header h2,html.dark .settings-group legend,html.dark .settings-option,html.dark .settings-field{color:var(--color-slate-100)}html.dark .settings-group{border-color:var(--color-slate-700)}html.dark .settings-field input{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .adminer-login-hint{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-400)}html.dark .adminer-login-row strong{color:var(--color-slate-100)}html.dark .modal-close,html.dark .settings-test-button,html.dark .settings-inline-button,html.dark .suggested-pick,html.dark .source-row-action,html.dark .source-picture-action{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}@media (hover:hover){:is(html.dark .modal-close,html.dark .settings-test-button,html.dark .settings-inline-button,html.dark .suggested-pick,html.dark .source-row-action,html.dark .source-picture-action):hover{background-color:var(--color-slate-700)}}html.dark .source-section{border-color:var(--color-slate-700)}html.dark .source-section-header{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .source-row{border-color:var(--color-slate-700);background-color:var(--color-slate-900);color:var(--color-slate-100)}@media (hover:hover){html.dark .source-row:hover{background-color:var(--color-slate-800)}}html.dark .suggested-row,html.dark .picture-candidate{border-color:var(--color-slate-700)}html.dark .suggested-row:nth-child(2n){background-color:var(--color-slate-800)}}@layer utilities{.collapse{visibility:collapse}.visible{visibility:visible}.fixed{position:fixed}.contents{display:contents}.grid{display:grid}.hidden{display:none}.table{display:table}}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-duration:initial;--tw-ease:initial;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-leading:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0}}}@layer theme{:root,:host{--font-sans:Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-100:oklch(93.6% .032 17.717);--color-red-300:oklch(80.8% .114 19.571);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-lime-700:oklch(53.2% .157 131.589);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-700:oklch(50.8% .118 165.612);--color-teal-50:oklch(98.4% .014 180.72);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-slate-950:oklch(12.9% .042 264.695);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-2xl:42rem;--container-3xl:48rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--radius-lg:.5rem;--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-catalog-surface:#f4f6f8;--color-catalog-panel-soft:#2d333b;--color-catalog-accent:#7ee34d;--color-catalog-accent-text:#10220b;--color-catalog-focus:#2f6fed}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*,:before,:after{box-sizing:border-box}html{color-scheme:light}html.dark{color-scheme:dark}body{min-height:100vh;font-family:var(--font-sans);color:#15181c;background:var(--color-catalog-surface);margin:0}button,input,select{font:inherit}}@layer components{.catalog-screen{background-color:var(--color-catalog-surface);min-height:100vh;padding-left:calc(var(--spacing) * 72);--secondary-track:minmax(240px,292px);--workspace-track:minmax(0,1fr);grid-template-columns:var(--secondary-track) var(--workspace-track);display:grid;position:relative}.catalog-screen.sidebar-collapsed{padding-left:calc(var(--spacing) * 11)}.catalog-screen.secondary-collapsed{--secondary-track:44px}.catalog-screen.workspace-collapsed{--workspace-track:44px}.sidebar-drawer{z-index:40;min-height:100vh;width:calc(var(--spacing) * 72);gap:calc(var(--spacing) * 1.5);background-color:var(--color-slate-900);padding:calc(var(--spacing) * 1.5);color:var(--color-slate-200);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));--tw-duration:.2s;--tw-ease:var(--ease-out);transition-duration:.2s;transition-timing-function:var(--ease-out);inset-block:0;flex-direction:column;font-size:11px;display:flex;position:fixed;left:0;overflow-y:auto}.panel-header,.sidebar-drawer-header,.column-header{box-sizing:border-box;height:calc(var(--spacing) * 7);min-height:calc(var(--spacing) * 7);justify-content:space-between;align-items:center;gap:var(--spacing);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-900);padding-inline:var(--spacing);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide);color:var(--color-slate-300);text-transform:uppercase;display:flex}.sidebar-drawer-header{margin-inline:calc(var(--spacing) * -1.5);margin-top:calc(var(--spacing) * -1.5);margin-bottom:calc(var(--spacing) * 1.5)}.sidebar-drawer-title{padding-inline:var(--spacing);color:var(--color-slate-300)}.sidebar-close-button,.sidebar-open-button{height:calc(var(--spacing) * 7);width:calc(var(--spacing) * 7);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-200);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;justify-content:center;align-items:center;display:inline-flex}@media (hover:hover){:is(.sidebar-close-button,.sidebar-open-button):hover{border-color:var(--color-slate-500);background-color:var(--color-slate-700)}}.sidebar-close-button .sidebar-icon,.sidebar-open-button .sidebar-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4)}.panel-control-button{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-400);background-color:var(--color-white);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1;color:var(--color-slate-700);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;flex-shrink:0;justify-content:center;align-items:center;line-height:1;display:inline-flex}@media (hover:hover){.panel-control-button:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-200)}}.panel-control-button:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.panel-control-button:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.panel-control-button:focus{--tw-outline-style:none;outline-style:none}.sidebar-open-button{top:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2);z-index:30;position:fixed}.sidebar-overlay{z-index:30;background-color:#02061880;position:fixed;inset:0}@supports (color:color-mix(in lab, red, red)){.sidebar-overlay{background-color:color-mix(in oklab, var(--color-slate-950) 50%, transparent)}}.sidebar-overlay[hidden]{display:none}.sidebar-drawer[aria-hidden=true]{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.sidebar-section{border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-700);padding-top:var(--spacing)}.drawer-trigger{min-height:calc(var(--spacing) * 8);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);width:100%;padding-inline:calc(var(--spacing) * 8);text-align:center;--tw-font-weight:var(--font-weight-semibold);font-size:11px;font-weight:var(--font-weight-semibold);color:var(--color-slate-100);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;justify-content:center;align-items:center;display:flex;position:relative}@media (hover:hover){.drawer-trigger:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-700)}}.drawer-trigger .sidebar-icon{position:static}.drawer-trigger>span:not(.drawer-chevron){text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.drawer-trigger .drawer-chevron{right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);--tw-leading:1;color:var(--color-slate-300);border-bottom-width:2px;font-size:0;line-height:1;display:inline-flex;position:absolute;rotate:45deg}.drawer-trigger[aria-expanded=true] .drawer-chevron{rotate:225deg}.parser-drawer[hidden]{display:none}.sidebar-icon{height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-leading:1;color:var(--color-slate-300);flex-shrink:0;justify-content:center;align-items:center;line-height:1;display:inline-flex}.action-button{justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);display:flex}.action-button>.sidebar-icon{flex-shrink:0}.action-button>span{text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.action-button .sidebar-icon{color:var(--color-slate-400)}.sidebar-footer{gap:var(--spacing);border-top-style:var(--tw-border-style);border-top-width:1px;border-color:var(--color-slate-700);padding-top:calc(var(--spacing) * 2);margin-top:auto;display:grid}.app-status{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-950);padding:var(--spacing);--tw-leading:var(--leading-tight);font-size:10px;line-height:var(--leading-tight);color:var(--color-slate-100);border-radius:.25rem}.database-status-content{justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);text-align:center;display:flex}.database-state-dot{height:calc(var(--spacing) * 2.5);width:calc(var(--spacing) * 2.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-500);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.database-state-dot.is-ready{border-color:var(--color-emerald-300);background-color:var(--color-emerald-500)}.database-state-dot.is-pending{border-color:var(--color-amber-300);background-color:var(--color-amber-400)}.database-state-dot.is-error{border-color:var(--color-red-300);background-color:var(--color-red-500)}.safe-mode,#modePill{background-color:var(--color-slate-100);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-semibold);font-size:10px;font-weight:var(--font-weight-semibold);color:var(--color-slate-800);border-radius:.25rem;display:inline-block}#databaseStatus.database-error{background-color:var(--color-red-100);color:var(--color-red-700)}.record-id{height:calc(var(--spacing) * 10);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-lime-700);background-color:var(--color-catalog-accent);--tw-leading:1;--tw-font-weight:var(--font-weight-bold);font-size:26px;line-height:1;font-weight:var(--font-weight-bold);color:var(--color-catalog-accent-text);border-radius:.25rem;justify-content:center;align-items:center;display:flex}.arrow-row{gap:var(--spacing);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.arrow-button,.action-button,.input-like{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);text-align:center;width:100%;color:var(--color-slate-200);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem}.arrow-button,.action-button{min-height:calc(var(--spacing) * 7);padding-inline:var(--spacing);font-size:11px}.arrow-button{color:var(--color-slate-100);position:relative}.arrow-button:before,.column-toggle:before{content:"";height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);border-bottom-width:2px;display:block}.arrow-button:before{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);border-bottom-width:2px;position:absolute;top:50%;left:50%}.arrow-button-prev:before{rotate:135deg}.arrow-button-next:before{rotate:-45deg}.parser-actions{gap:var(--spacing);display:grid}.parser-actions .action-button{min-height:calc(var(--spacing) * 7)}.action-button.primary{min-height:calc(var(--spacing) * 8);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.arrow-button:hover:not(:disabled),.action-button:hover:not(:disabled){border-color:var(--color-slate-500);background-color:var(--color-slate-700);color:var(--color-white)}.arrow-button:disabled,.action-button:disabled{cursor:not-allowed;opacity:.5}.input-like{height:calc(var(--spacing) * 7);padding-inline:var(--spacing);font-size:11px}.select-control{width:100%;position:relative}.select-display{pointer-events:none;z-index:30;justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);text-align:center;color:var(--color-slate-200);display:flex;position:absolute;inset:0}.select-display span{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:calc(100% - 3rem);overflow:hidden}.language-native-select{z-index:20;cursor:pointer;appearance:none;padding-right:calc(var(--spacing) * 7);padding-left:calc(var(--spacing) * 7);caret-color:#0000;background-color:#0000;position:relative;color:#0000!important}.language-native-select:focus{color:#0000!important}.select-chevron{pointer-events:none;top:50%;right:calc(var(--spacing) * 3);z-index:30;height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);color:var(--color-slate-200);border-bottom-width:2px;position:absolute;rotate:45deg}.native-manufacturer-host,.native-manufacturer-select{display:none}.manufacturer-picker{position:relative}.manufacturer-trigger{height:calc(var(--spacing) * 7);justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-700);background-color:var(--color-slate-800);width:100%;padding-inline:calc(var(--spacing) * 9);text-align:center;color:var(--color-slate-200);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:.25rem;font-size:11px;display:flex;position:relative}.manufacturer-trigger>span:not(.select-chevron){text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.manufacturer-trigger:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-700)}.manufacturer-trigger[aria-expanded=true]{border-color:var(--color-catalog-focus);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.manufacturer-trigger[aria-expanded=true]{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.manufacturer-trigger .select-chevron{top:50%;right:calc(var(--spacing) * 3);--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y);margin-left:0}.manufacturer-menu{z-index:50;margin-top:var(--spacing);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);padding:var(--spacing);--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:.25rem;position:absolute;top:100%;left:0;right:0}.manufacturer-menu[hidden]{display:none}.manufacturer-search{margin-bottom:var(--spacing);height:calc(var(--spacing) * 7);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);width:100%;padding-inline:calc(var(--spacing) * 2);color:var(--color-slate-800);--tw-outline-style:none;border-radius:.25rem;outline-style:none;font-size:11px}.manufacturer-search:focus{border-color:var(--color-catalog-focus);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.manufacturer-search:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.manufacturer-options{max-height:calc(var(--spacing) * 60);overflow-y:auto}.manufacturer-option{min-height:calc(var(--spacing) * 7);text-overflow:ellipsis;white-space:nowrap;width:100%;padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);text-align:left;color:var(--color-slate-700);border-radius:.25rem;font-size:11px;display:block;overflow:hidden}@media (hover:hover){.manufacturer-option:hover{background-color:var(--color-slate-100)}}.manufacturer-option[aria-selected=true]{background-color:var(--color-slate-200);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-slate-900)}.manufacturer-empty{padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * 2);color:var(--color-slate-500);font-size:11px}#scanInput{background-color:var(--color-slate-700)}#scanInput::placeholder{color:var(--color-slate-400)}.input-like:focus{border-color:var(--color-catalog-focus);--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.input-like:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.input-like:focus{--tw-outline-style:none;outline-style:none}.picture-slot{aspect-ratio:1;border-style:var(--tw-border-style);--tw-border-style:dashed;border-style:dashed;border-width:1px;border-color:var(--color-slate-500);background-color:var(--color-catalog-panel-soft);width:100%;padding:var(--spacing);text-align:center;color:var(--color-slate-200);border-radius:.25rem;font-size:11px;overflow:hidden}.picture-slot img{object-fit:contain;width:100%;height:100%}.mapping-box{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-500);background-color:var(--color-catalog-panel-soft);border-radius:.25rem;position:relative}.sidebar-mapping-box{border-color:var(--color-slate-700);background-color:var(--color-slate-900);overflow:visible}.mapping-details{position:relative}.mapping-summary{min-height:calc(var(--spacing) * 8);background-color:var(--color-slate-800);padding-inline:calc(var(--spacing) * 9);text-align:center;--tw-font-weight:var(--font-weight-semibold);font-size:11px;font-weight:var(--font-weight-semibold);color:var(--color-slate-100);border-radius:.25rem;justify-content:center;align-items:center;display:flex;position:relative}.mapping-label{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);max-width:calc(100% - 4.5rem);translate:var(--tw-translate-x) var(--tw-translate-y);justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);text-align:center;display:inline-flex;position:absolute;top:50%;left:50%}.mapping-title{text-overflow:ellipsis;white-space:nowrap;text-align:center;min-width:0;overflow:hidden}.mapping-state-dot{height:calc(var(--spacing) * 3);width:calc(var(--spacing) * 3);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-500);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:3.40282e38px;flex-shrink:0;display:inline-block}.mapping-state-dot.is-ready{border-color:var(--color-emerald-300);background-color:var(--color-emerald-500)}.mapping-state-dot.is-warning{border-color:var(--color-amber-300);background-color:var(--color-amber-400)}.mapping-state-dot.is-missing{border-color:var(--color-red-300);background-color:var(--color-red-500)}.mapping-state-dot.is-loading{border-color:var(--color-slate-300);background-color:var(--color-slate-400)}.mapping-panel{z-index:50;border-style:var(--tw-border-style);border-width:1px;border-top-style:var(--tw-border-style);border-top-width:0;border-color:var(--color-slate-700);background-color:#020618f2;position:absolute;top:100%;left:0;right:0}@supports (color:color-mix(in lab, red, red)){.mapping-panel{background-color:color-mix(in oklab, var(--color-slate-950) 95%, transparent)}}.mapping-panel{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.mapping-detail-status{text-overflow:ellipsis;white-space:nowrap;padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);text-align:center;--tw-font-weight:var(--font-weight-normal);font-size:10px;font-weight:var(--font-weight-normal);color:var(--color-slate-300);overflow:hidden}.mapping-summary .mapping-toggle{top:50%;right:calc(var(--spacing) * 2);height:calc(var(--spacing) * 4);width:calc(var(--spacing) * 4);--tw-translate-y:calc(calc(1 / 2 * 100%) * -1);translate:var(--tw-translate-x) var(--tw-translate-y);justify-content:center;align-items:center;display:flex;position:absolute}.mapping-details summary{cursor:pointer;list-style-type:none}.mapping-details summary::-webkit-details-marker{display:none}.mapping-toggle:before{content:"";height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-right-style:var(--tw-border-style);border-right-width:2px;border-bottom-style:var(--tw-border-style);border-bottom-width:2px;border-color:var(--color-slate-100);display:block;rotate:45deg}.mapping-list{gap:calc(var(--spacing) * .5);padding-inline:var(--spacing);padding-bottom:var(--spacing);display:grid}.mapping-source-row{align-items:center;gap:var(--spacing);background-color:var(--color-slate-100);padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);color:var(--color-slate-800);border-radius:.25rem;grid-template-columns:minmax(0,1fr) auto;font-size:10px;display:grid}.mapping-source-row.is-disabled{opacity:.5}.mapping-source-name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mapping-source-status{padding-inline:var(--spacing);--tw-font-weight:var(--font-weight-semibold);font-size:9px;font-weight:var(--font-weight-semibold);border-radius:.25rem}.mapping-source-status.is-ok{background-color:var(--color-emerald-100);color:var(--color-emerald-700)}.mapping-source-status.is-bad{background-color:var(--color-red-100);color:var(--color-red-700)}.mapping-source-status.is-muted{background-color:var(--color-slate-200);color:var(--color-slate-600)}.secondary-panel{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-slate-400);background-color:var(--color-slate-300);min-width:0}.column-header{min-width:0}.column-toggle{height:calc(var(--spacing) * 5);width:calc(var(--spacing) * 5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-400);background-color:var(--color-white);--tw-leading:1;color:var(--color-slate-700);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));border-radius:.25rem;flex-shrink:0;justify-content:center;align-items:center;font-size:0;line-height:1;display:inline-flex}@media (hover:hover){.column-toggle:hover{border-color:var(--color-slate-500);background-color:var(--color-slate-200)}}.column-toggle:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#2f6fed40}@supports (color:color-mix(in lab, red, red)){.column-toggle:focus{--tw-ring-color:color-mix(in oklab, var(--color-catalog-focus) 25%, transparent)}}.column-toggle:focus{--tw-outline-style:none;outline-style:none}#secondaryPanelToggle[aria-label="Collapse product details"]:before,#mainWorkspaceToggle[aria-label="Collapse main workspace"]:before{transform:rotate(135deg)}#secondaryPanelToggle[aria-label="Expand product details"]:before,#mainWorkspaceToggle[aria-label="Expand main workspace"]:before{transform:rotate(-45deg)}.secondary-panel .field-grid{overflow:hidden}.secondary-collapsed .secondary-panel .column-header{height:100%;padding-block:var(--spacing);writing-mode:vertical-rl;flex-direction:column;justify-content:flex-start}.secondary-collapsed .secondary-panel .column-header .column-toggle,.workspace-collapsed .main-workspace .workspace-header .column-toggle{writing-mode:horizontal-tb}.secondary-collapsed .secondary-panel .field-grid{display:none}.workspace-collapsed .main-workspace .workspace-header{height:100%;padding-block:var(--spacing);writing-mode:vertical-rl;flex-direction:column;justify-content:flex-start}.workspace-collapsed .main-workspace .workspace-header span,.workspace-collapsed .main-workspace .table-wrap{display:none}.field-grid{grid-template-columns:132px minmax(0,168px);align-content:flex-start;display:grid}.field-label,.field-value{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);min-height:22px;padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);--tw-leading:var(--leading-tight);font-size:11px;line-height:var(--leading-tight)}.field-label{background-color:var(--color-slate-200);color:var(--color-slate-800)}.field-value{background-color:var(--color-teal-50);text-align:right;color:var(--color-slate-900)}.field-value[data-field=manufacturer_name]{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.main-workspace{background-color:var(--color-catalog-surface);flex-direction:column;min-width:0;max-height:100vh;display:flex;overflow:hidden}.data-panel{background-color:var(--color-catalog-surface);min-width:0;overflow:hidden}.table-wrap{scrollbar-gutter:stable;flex:1;min-height:0;overflow:auto}.variant-grid{min-width:0;min-width:var(--variant-min-width,820px);font-size:11px}.variant-header,.variant-row{grid-template-columns:var(--variant-columns);display:grid}.variant-header{z-index:10;position:sticky;top:0}.variant-header-cell{border-right-style:var(--tw-border-style);border-right-width:1px;border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-200);height:22px;padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);white-space:nowrap;color:var(--color-slate-800)}.variant-row{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);min-height:22px}.variant-row:nth-child(2n){background-color:var(--color-slate-100)}.variant-row:hover{background-color:var(--color-slate-200)}.variant-cell{border-right-style:var(--tw-border-style);border-right-width:1px;border-color:var(--color-slate-300);min-width:0;padding-inline:var(--spacing);padding-block:calc(var(--spacing) * .5);text-align:center;white-space:nowrap;color:var(--color-slate-800)}.variant-cell[data-label=""]{padding:0}.empty-row{padding-block:calc(var(--spacing) * 4);text-align:center;color:var(--color-slate-500)}.variant-cell.problem-ean{background-color:var(--color-red-600);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-white)}.modal-backdrop{z-index:50;background-color:#0f172b80;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}@supports (color:color-mix(in lab, red, red)){.modal-backdrop{background-color:color-mix(in oklab, var(--color-slate-900) 50%, transparent)}}.modal-backdrop{padding:calc(var(--spacing) * 4)}.modal-backdrop[hidden]{display:none}.suggested-dialog,.source-dialog,.settings-dialog{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-50);--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);width:100%;max-height:90vh;box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);overflow:hidden}.source-dialog-actions{align-items:center;gap:calc(var(--spacing) * 1.5);display:flex}.suggested-dialog{max-width:var(--container-2xl)}.source-dialog{max-width:var(--container-3xl)}.settings-dialog{max-width:var(--container-md)}.suggested-header{border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 2);justify-content:space-between;align-items:center;display:flex}.suggested-header h2{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-800);margin:0}.modal-close,.settings-test-button,.settings-inline-button{justify-content:center;align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);color:var(--color-slate-800);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);border-radius:.25rem;font-size:11px;display:inline-flex}@media (hover:hover){:is(.modal-close,.settings-test-button,.settings-inline-button):hover{background-color:var(--color-slate-100)}}.modal-close .sidebar-icon,.settings-test-button .sidebar-icon,.settings-inline-button .sidebar-icon{height:calc(var(--spacing) * 3.5);width:calc(var(--spacing) * 3.5);color:var(--color-slate-500)}.suggested-list,.source-list,.settings-content{max-height:calc(90vh - 48px);padding:calc(var(--spacing) * 2);overflow-y:auto}.settings-content{gap:calc(var(--spacing) * 2);display:grid}.settings-group{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);padding:calc(var(--spacing) * 2);border-radius:.25rem;margin:0}.settings-group legend{padding-inline:var(--spacing);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-700)}.settings-option{align-items:center;gap:var(--spacing);padding-block:calc(var(--spacing) * .5);color:var(--color-slate-700);font-size:11px;display:flex}.settings-option .settings-inline-button{padding-block:calc(var(--spacing) * .5);margin-left:auto;font-size:10px}.settings-tool-actions{align-items:center;gap:var(--spacing);margin-left:auto;display:flex}.settings-tool-actions .settings-inline-button{margin-left:0}.settings-option input{accent-color:var(--color-catalog-focus)}.settings-option input:disabled+span{color:var(--color-slate-400)}.local-db-fields{gap:var(--spacing);display:grid}.local-db-fields[hidden]{display:none}.settings-field{align-items:center;gap:calc(var(--spacing) * 2);color:var(--color-slate-700);grid-template-columns:72px minmax(0,1fr);font-size:11px;display:grid}.settings-field input{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);min-width:0;padding-inline:var(--spacing);padding-block:var(--spacing);border-radius:.25rem;font-size:11px}.settings-select{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);min-width:0;padding-inline:var(--spacing);padding-block:var(--spacing);color:var(--color-slate-800);border-radius:.25rem;font-size:11px}.settings-note,.settings-permissions{--tw-leading:var(--leading-tight);font-size:10px;line-height:var(--leading-tight);color:var(--color-slate-500);margin:0}.settings-permissions{gap:calc(var(--spacing) * .5);display:grid}.adminer-login-hint{margin-top:var(--spacing);gap:calc(var(--spacing) * .5);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-slate-100);padding:calc(var(--spacing) * 1.5);--tw-leading:var(--leading-tight);font-size:10px;line-height:var(--leading-tight);color:var(--color-slate-600);border-radius:.25rem;display:grid}.adminer-login-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);display:flex}.adminer-login-row strong{text-overflow:ellipsis;white-space:nowrap;text-align:right;--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold);color:var(--color-slate-800);overflow:hidden}.connection-ok{color:var(--color-emerald-700)}.connection-error{color:var(--color-red-700)}.suggested-row{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:var(--spacing);padding-block:var(--spacing);grid-template-columns:40px minmax(0,1fr) auto;font-size:11px;display:grid}.suggested-row:nth-child(2n){background-color:var(--color-slate-100)}.suggested-row img{height:calc(var(--spacing) * 9);width:calc(var(--spacing) * 9);object-fit:contain}.suggested-id,.suggested-active{color:var(--color-slate-500)}.suggested-name,.suggested-reference{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.suggested-pick,.source-row-action,.source-picture-action{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 1.5);padding-block:calc(var(--spacing) * .5);border-radius:.25rem;font-size:10px}@media (hover:hover){:is(.suggested-pick,.source-row-action,.source-picture-action):hover{background-color:var(--color-slate-100)}}.source-section{margin-bottom:calc(var(--spacing) * 2);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);border-radius:.25rem;overflow:hidden}.source-section-header,.source-row{justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:calc(var(--spacing) * 2);padding-block:var(--spacing);font-size:11px;display:flex}.source-section-header{background-color:var(--color-slate-200);--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.source-section-actions{align-items:center;gap:var(--spacing);display:flex}.source-row{background-color:var(--color-white)}@media (hover:hover){.source-row:hover{background-color:var(--color-slate-100)}}.source-row-title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.source-status,.source-row-action{padding-inline:var(--spacing);border-radius:.25rem;flex-shrink:0;font-size:10px}.source-status.is-ok{background-color:var(--color-emerald-100);color:var(--color-emerald-700)}.source-status.is-muted{background-color:var(--color-slate-200);color:var(--color-slate-600)}.picture-candidate{align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding:calc(var(--spacing) * 2);grid-template-columns:72px minmax(0,1fr) auto;font-size:10px;display:grid}.picture-candidate-preview{height:calc(var(--spacing) * 16);background-color:var(--color-white);justify-content:center;align-items:center;width:72px;display:flex}.picture-candidate img{max-height:calc(var(--spacing) * 16);object-fit:contain;width:72px}.picture-candidate-meta{gap:calc(var(--spacing) * .5);min-width:0;display:grid}.picture-candidate-title{text-overflow:ellipsis;white-space:nowrap;--tw-font-weight:var(--font-weight-semibold);min-width:0;font-weight:var(--font-weight-semibold);color:var(--color-slate-800);overflow:hidden}.picture-candidate-dimensions,.picture-candidate-target,.picture-candidate-duplicate{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--color-slate-500);overflow:hidden}.picture-candidate-save{min-width:76px}@media (max-width:980px){.catalog-screen{flex-direction:column;padding-left:0;display:flex}.catalog-screen.sidebar-collapsed{padding-left:0}.sidebar-drawer{width:min(18rem,100vw - 1rem);min-height:0}.sidebar-drawer:not([aria-hidden=true]){max-height:100vh}.sidebar-drawer[aria-hidden=true]{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.sidebar-open-button{top:calc(var(--spacing) * 2);left:calc(var(--spacing) * 2)}.secondary-panel{border-right-style:var(--tw-border-style);border-right-width:0;order:1;padding-top:0}.main-workspace{order:2}.table-wrap{max-height:70vh}.column-header{height:calc(var(--spacing) * 7);writing-mode:horizontal-tb}.secondary-collapsed .secondary-panel .column-header,.workspace-collapsed .main-workspace .workspace-header{height:calc(var(--spacing) * 7);writing-mode:horizontal-tb;flex-direction:row;padding-block:0}.secondary-collapsed .secondary-panel .column-header span,.workspace-collapsed .main-workspace .workspace-header span{display:inline}.workspace-collapsed .main-workspace .table-wrap{display:none}.variant-grid{min-width:0}.variant-header{display:none}.variant-row{column-gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-300);padding:calc(var(--spacing) * 2);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.variant-cell{min-height:calc(var(--spacing) * 7);justify-content:space-between;align-items:center;gap:calc(var(--spacing) * 2);border-bottom-style:var(--tw-border-style);border-bottom-width:1px;border-color:var(--color-slate-200);padding-inline:var(--spacing);padding-block:var(--spacing);text-align:right;white-space:normal;display:flex}.variant-cell:before{content:attr(data-label);text-align:left;--tw-font-weight:var(--font-weight-medium);font-size:10px;font-weight:var(--font-weight-medium);color:var(--color-slate-500);flex-shrink:0}.variant-cell[data-label=""]:before{content:none}.variant-cell[data-label=""]{grid-column:span 2/span 2}.variant-message-row{display:block}html.dark .variant-row,html.dark .variant-cell{border-color:var(--color-slate-700)}}@media (min-width:981px){.sidebar-overlay{display:none}}html:not(.dark) .control-panel{background-color:var(--color-slate-100);color:var(--color-slate-700)}html:not(.dark) .sidebar-drawer-header,html:not(.dark) .sidebar-footer{border-color:var(--color-slate-300)}html:not(.dark) .panel-header,html:not(.dark) .sidebar-drawer-header,html:not(.dark) .column-header{border-color:var(--color-slate-300);background-color:var(--color-slate-100);color:var(--color-slate-700)}html:not(.dark) .sidebar-drawer-title,html:not(.dark) .drawer-trigger .drawer-chevron,html:not(.dark) .select-chevron,html:not(.dark) .select-display,html:not(.dark) .manufacturer-trigger .sidebar-icon{color:var(--color-slate-700)}html:not(.dark) .sidebar-close-button,html:not(.dark) .sidebar-open-button,html:not(.dark) .drawer-trigger,html:not(.dark) .arrow-button,html:not(.dark) .action-button,html:not(.dark) .input-like{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .panel-control-button{border-color:var(--color-slate-400);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .sidebar-close-button:hover,html:not(.dark) .sidebar-open-button:hover,html:not(.dark) .drawer-trigger:hover,html:not(.dark) .arrow-button:hover:not(:disabled),html:not(.dark) .action-button:hover:not(:disabled){border-color:var(--color-slate-400);background-color:var(--color-slate-200);color:var(--color-slate-900)}html:not(.dark) .arrow-button:before{color:var(--color-slate-700)}html:not(.dark) #scanInput{background-color:var(--color-slate-200)}html:not(.dark) #scanInput::placeholder{color:var(--color-slate-500)}html:not(.dark) .picture-slot,html:not(.dark) .mapping-box{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-600)}html:not(.dark) .mapping-toggle:before{border-color:var(--color-slate-700)}html:not(.dark) .app-status{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .sidebar-mapping-box{border-color:var(--color-slate-300);background-color:var(--color-slate-100)}html:not(.dark) .mapping-summary{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .mapping-panel{border-color:var(--color-slate-300);background-color:var(--color-white)}html:not(.dark) .mapping-detail-status{color:var(--color-slate-600)}html:not(.dark) .manufacturer-trigger{border-color:var(--color-slate-300);background-color:var(--color-white);color:var(--color-slate-700)}html:not(.dark) .manufacturer-trigger:hover{border-color:var(--color-slate-400);background-color:var(--color-slate-200)}html.contrast-light-high:not(.dark) .control-panel{background-color:var(--color-slate-200);color:var(--color-slate-900)}html.contrast-light-high:not(.dark) .action-button,html.contrast-light-high:not(.dark) .arrow-button,html.contrast-light-high:not(.dark) .input-like,html.contrast-light-high:not(.dark) .manufacturer-trigger{border-color:var(--color-slate-500);color:var(--color-slate-900)}html.contrast-light-high:not(.dark) .sidebar-icon{color:var(--color-slate-700)}html.dark body{background-color:var(--color-slate-950);color:var(--color-slate-100)}html.dark .control-panel{background-color:var(--color-slate-950)}html.dark .secondary-panel{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}html.dark .data-panel{background-color:var(--color-slate-950)}html.dark .field-label{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-200)}html.dark .field-value{border-color:var(--color-slate-700);background-color:var(--color-slate-900);color:var(--color-slate-100)}html.dark .variant-header-cell{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .variant-row{border-color:var(--color-slate-700)}html.dark .variant-row:nth-child(2n){background-color:var(--color-slate-800)}html.dark .variant-row:hover{background-color:var(--color-slate-700)}html.dark .variant-cell{border-color:var(--color-slate-700);color:var(--color-slate-200)}html.dark .arrow-button,html.dark .action-button,html.dark .input-like{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .arrow-button:hover:not(:disabled),html.dark .action-button:hover:not(:disabled){border-color:var(--color-slate-400);background-color:var(--color-slate-700)}html.dark #scanInput{background-color:var(--color-slate-700)}html.dark #scanInput::placeholder{color:var(--color-slate-400)}html.dark .manufacturer-menu{border-color:var(--color-slate-700);background-color:var(--color-slate-900)}html.dark .manufacturer-search{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .manufacturer-option{color:var(--color-slate-200)}@media (hover:hover){html.dark .manufacturer-option:hover{background-color:var(--color-slate-800)}}html.dark .manufacturer-option[aria-selected=true]{background-color:var(--color-slate-700);color:var(--color-white)}html.dark .column-header{border-color:var(--color-slate-700);color:var(--color-slate-300)}html.dark .panel-control-button,html.dark .column-toggle{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}@media (hover:hover){:is(html.dark .panel-control-button,html.dark .column-toggle):hover{border-color:var(--color-slate-400);background-color:var(--color-slate-700)}}html.contrast-dark-high.dark .control-panel{background-color:var(--color-slate-950);color:var(--color-white)}html.contrast-dark-high.dark .action-button,html.contrast-dark-high.dark .arrow-button,html.contrast-dark-high.dark .input-like,html.contrast-dark-high.dark .manufacturer-trigger{border-color:var(--color-slate-500);background-color:var(--color-slate-700);color:var(--color-white)}html.contrast-dark-high.dark .sidebar-icon{color:var(--color-slate-100)}html.dark .settings-dialog,html.dark .suggested-dialog,html.dark .source-dialog{border-color:var(--color-slate-700);background-color:var(--color-slate-900)}html.dark .suggested-header{border-color:var(--color-slate-700);background-color:var(--color-slate-800)}html.dark .suggested-header h2,html.dark .settings-group legend,html.dark .settings-option,html.dark .settings-field{color:var(--color-slate-100)}html.dark .settings-group{border-color:var(--color-slate-700)}html.dark .settings-field input{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .adminer-login-hint{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-400)}html.dark .adminer-login-row strong{color:var(--color-slate-100)}html.dark .modal-close,html.dark .settings-test-button,html.dark .settings-inline-button,html.dark .suggested-pick,html.dark .source-row-action,html.dark .source-picture-action{border-color:var(--color-slate-600);background-color:var(--color-slate-800);color:var(--color-slate-100)}@media (hover:hover){:is(html.dark .modal-close,html.dark .settings-test-button,html.dark .settings-inline-button,html.dark .suggested-pick,html.dark .source-row-action,html.dark .source-picture-action):hover{background-color:var(--color-slate-700)}}html.dark .source-section{border-color:var(--color-slate-700)}html.dark .source-section-header{border-color:var(--color-slate-700);background-color:var(--color-slate-800);color:var(--color-slate-100)}html.dark .source-row{border-color:var(--color-slate-700);background-color:var(--color-slate-900);color:var(--color-slate-100)}@media (hover:hover){html.dark .source-row:hover{background-color:var(--color-slate-800)}}html.dark .suggested-row,html.dark .picture-candidate{border-color:var(--color-slate-700)}html.dark .picture-candidate-preview{background-color:var(--color-slate-800)}html.dark .picture-candidate-title{color:var(--color-slate-100)}html.dark .picture-candidate-dimensions,html.dark .picture-candidate-target,html.dark .picture-candidate-duplicate{color:var(--color-slate-400)}html.dark .suggested-row:nth-child(2n){background-color:var(--color-slate-800)}}@layer utilities{.collapse{visibility:collapse}.visible{visibility:visible}.fixed{position:fixed}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.contents{display:contents}.grid{display:grid}.hidden{display:none}.table{display:table}}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-leading{syntax:"*";inherits:false}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0} \ No newline at end of file diff --git a/src/db/scrape-image-store.ts b/src/db/scrape-image-store.ts new file mode 100644 index 0000000..b06368e --- /dev/null +++ b/src/db/scrape-image-store.ts @@ -0,0 +1,319 @@ +// @ts-nocheck +import crypto from "node:crypto"; +import mariadb from "mariadb"; + +export async function saveScrapePictureCandidate(config, picture) { + if (config.database?.driver !== "mariadb") { + throw new Error("Local scrape DB needs MariaDB configuration."); + } + if (!config.database?.allowLocalWrites) { + throw new Error("Local scrape DB writes are disabled. Enable writes in Settings first."); + } + + const imageUrl = normalizeRequiredUrl(picture.imageUrl, "imageUrl"); + const sourceKey = normalizeText(picture.sourceKey) || "unknown"; + const sourceName = normalizeText(picture.sourceName) || sourceKey; + const sourcePageUrl = normalizeOptionalText(picture.pageUrl); + const image = await downloadImage(imageUrl); + const sha256 = crypto.createHash("sha256").update(image.buffer).digest("hex"); + const scrapeDb = config.database.scrapeName || "catalog_scrape"; + + const connection = await mariadb.createConnection({ + host: config.database.host, + port: Number(config.database.port), + database: scrapeDb, + user: config.database.user, + password: config.database.password, + bigIntAsNumber: true, + }); + + try { + await connection.beginTransaction(); + const sourceId = await upsertSource(connection, { + sourceKey, + sourceName, + imageUrl, + sourceType: normalizeText(picture.sourceType) || "supplier", + }); + const imageBlobId = await upsertImageBlob(connection, { + sha256, + mimeType: image.mimeType, + width: normalizePositiveNumber(picture.width), + height: normalizePositiveNumber(picture.height), + sizeBytes: image.buffer.byteLength, + buffer: image.buffer, + }); + const duplicate = await findExistingImageCandidate(connection, imageBlobId, sourceId, imageUrl); + const candidateId = await upsertPictureCandidate(connection, { + imageBlobId, + sourceId, + manufacturerId: normalizePositiveNumber(picture.manufacturerId), + idProductCatalog: normalizePositiveNumber(picture.idProductCatalog), + idProduct: normalizePositiveNumber(picture.idProduct), + supplierReference: normalizeOptionalText(picture.supplierReference), + ean13: normalizeOptionalText(picture.ean13), + color: normalizeOptionalText(picture.color), + sizeValue: normalizeOptionalText(picture.sizeValue), + combi: normalizeOptionalText(picture.combi), + sourcePageUrl, + sourceImageUrl: imageUrl, + sourcePosition: normalizePositiveNumber(picture.sourcePosition) || 0, + targetStrategy: normalizeTargetStrategy(picture.targetStrategy), + targetColor: normalizeOptionalText(picture.targetColor), + targetSize: normalizeOptionalText(picture.targetSize), + targetCombi: normalizeOptionalText(picture.targetCombi), + targetEan13: normalizeOptionalText(picture.targetEan13), + targetIdProductCatalog: normalizePositiveNumber(picture.targetIdProductCatalog), + matchConfidence: normalizeConfidence(picture.matchConfidence), + duplicateStatus: duplicate ? "exact_duplicate" : "unique", + duplicateOfCandidateId: duplicate?.id || null, + }); + + await connection.commit(); + return { + saved: true, + candidateId, + imageBlobId, + sha256, + duplicateStatus: duplicate ? "exact_duplicate" : "unique", + duplicateOfCandidateId: duplicate?.id || null, + mimeType: image.mimeType, + width: normalizePositiveNumber(picture.width) || null, + height: normalizePositiveNumber(picture.height) || null, + sizeBytes: image.buffer.byteLength, + sourceKey, + sourceName, + }; + } catch (error) { + await connection.rollback(); + throw error; + } finally { + await connection.end(); + } +} + +async function downloadImage(url) { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 20000); + try { + const response = await fetch(url, { + signal: controller.signal, + headers: { + accept: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8", + }, + }); + if (!response.ok) { + throw new Error(`Image download failed with HTTP ${response.status}.`); + } + + const mimeType = String(response.headers.get("content-type") || "application/octet-stream") + .split(";")[0] + .toLowerCase(); + if (!mimeType.startsWith("image/")) { + throw new Error(`Downloaded content is not an image (${mimeType}).`); + } + + const buffer = Buffer.from(await response.arrayBuffer()); + if (!buffer.length) { + throw new Error("Downloaded image is empty."); + } + if (buffer.byteLength > 20 * 1024 * 1024) { + throw new Error("Downloaded image is larger than 20 MB."); + } + + return { buffer, mimeType }; + } finally { + clearTimeout(timeout); + } +} + +async function upsertSource(connection, source) { + const baseUrl = new URL(source.imageUrl).origin; + const sourceType = ["supplier", "manufacturer", "backup", "other"].includes(source.sourceType) + ? source.sourceType + : "supplier"; + const result = await connection.query( + ` + INSERT INTO scrape_sources ( + source_key, + source_name, + source_type, + base_url, + enabled + ) + VALUES (?, ?, ?, ?, 1) + ON DUPLICATE KEY UPDATE + id = LAST_INSERT_ID(id), + source_name = VALUES(source_name), + base_url = VALUES(base_url), + enabled = VALUES(enabled) + `, + [source.sourceKey, source.sourceName, sourceType, baseUrl], + ); + + return Number(result.insertId); +} + +async function upsertImageBlob(connection, image) { + const result = await connection.query( + ` + INSERT INTO scrape_image_blobs ( + sha256, + mime_type, + width, + height, + size_bytes, + image_data + ) + VALUES (?, ?, ?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + id = LAST_INSERT_ID(id), + mime_type = VALUES(mime_type), + width = COALESCE(VALUES(width), width), + height = COALESCE(VALUES(height), height), + size_bytes = VALUES(size_bytes) + `, + [image.sha256, image.mimeType, image.width, image.height, image.sizeBytes, image.buffer], + ); + + return Number(result.insertId); +} + +async function findExistingImageCandidate(connection, imageBlobId, sourceId, imageUrl) { + const rows = await connection.query( + ` + SELECT id + FROM scrape_picture_candidates + WHERE image_blob_id = ? + AND NOT (source_id = ? AND source_image_url = ?) + ORDER BY id ASC + LIMIT 1 + `, + [imageBlobId, sourceId, imageUrl], + ); + const items = normalizeRows(rows); + return items[0] || null; +} + +async function upsertPictureCandidate(connection, candidate) { + const result = await connection.query( + ` + INSERT INTO scrape_picture_candidates ( + image_blob_id, + source_id, + manufacturer_id, + id_product_catalog, + id_product, + supplier_reference, + ean13, + color, + size_value, + combi, + source_page_url, + source_image_url, + source_position, + target_strategy, + target_color, + target_size, + target_combi, + target_ean13, + target_id_product_catalog, + match_confidence, + duplicate_status, + duplicate_of_candidate_id, + status + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'draft') + ON DUPLICATE KEY UPDATE + id = LAST_INSERT_ID(id), + image_blob_id = VALUES(image_blob_id), + manufacturer_id = VALUES(manufacturer_id), + id_product_catalog = VALUES(id_product_catalog), + id_product = VALUES(id_product), + supplier_reference = VALUES(supplier_reference), + ean13 = VALUES(ean13), + color = VALUES(color), + size_value = VALUES(size_value), + combi = VALUES(combi), + source_page_url = VALUES(source_page_url), + source_position = VALUES(source_position), + target_strategy = VALUES(target_strategy), + target_color = VALUES(target_color), + target_size = VALUES(target_size), + target_combi = VALUES(target_combi), + target_ean13 = VALUES(target_ean13), + target_id_product_catalog = VALUES(target_id_product_catalog), + match_confidence = VALUES(match_confidence), + duplicate_status = VALUES(duplicate_status), + duplicate_of_candidate_id = VALUES(duplicate_of_candidate_id), + updated_at = CURRENT_TIMESTAMP + `, + [ + candidate.imageBlobId, + candidate.sourceId, + candidate.manufacturerId, + candidate.idProductCatalog, + candidate.idProduct, + candidate.supplierReference, + candidate.ean13, + candidate.color, + candidate.sizeValue, + candidate.combi, + candidate.sourcePageUrl, + candidate.sourceImageUrl, + candidate.sourcePosition, + candidate.targetStrategy, + candidate.targetColor, + candidate.targetSize, + candidate.targetCombi, + candidate.targetEan13, + candidate.targetIdProductCatalog, + candidate.matchConfidence, + candidate.duplicateStatus, + candidate.duplicateOfCandidateId, + ], + ); + + return Number(result.insertId); +} + +function normalizeRows(rows) { + return Array.isArray(rows) ? rows.filter((row) => row && typeof row === "object" && !("meta" in row)) : []; +} + +function normalizeRequiredUrl(value, name) { + const text = normalizeText(value); + if (!text) throw new Error(`${name} is required.`); + const url = new URL(text); + if (!["http:", "https:"].includes(url.protocol)) { + throw new Error(`${name} must be an HTTP URL.`); + } + return url.toString(); +} + +function normalizeText(value) { + return String(value ?? "").trim(); +} + +function normalizeOptionalText(value) { + const text = normalizeText(value); + return text || null; +} + +function normalizePositiveNumber(value) { + const number = Number(value); + return Number.isFinite(number) && number > 0 ? Math.trunc(number) : null; +} + +function normalizeConfidence(value) { + const number = Number(value); + if (!Number.isFinite(number)) return 0; + return Math.max(0, Math.min(100, number)); +} + +function normalizeTargetStrategy(value) { + const strategy = normalizeText(value).toLowerCase(); + return ["unknown", "color", "size", "combination", "manual"].includes(strategy) + ? strategy + : "unknown"; +} diff --git a/src/server.ts b/src/server.ts index 95eaeb6..4a9944e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -15,6 +15,7 @@ import { loadProductByEan, loadProductInfo, getProductPictures, + saveProductPictureCandidate, openProductSource, } from "./services/catalog-products.ts"; import { openInBrowser } from "./services/browser-adapter.ts"; @@ -124,6 +125,12 @@ const server = http.createServer(async (request, response) => { return; } + if (url.pathname === "/api/catalog-maker/save-picture" && request.method === "POST") { + const body = await readJsonBody(request); + await sendJson(response, await saveProductPictureCandidate(requestConfig, body)); + return; + } + if (url.pathname === "/api/catalog-maker/open-source") { await sendJson( response, diff --git a/src/services/catalog-products.ts b/src/services/catalog-products.ts index a7aab45..3605004 100644 --- a/src/services/catalog-products.ts +++ b/src/services/catalog-products.ts @@ -1,5 +1,6 @@ // @ts-nocheck import { executeEndpointWrite, queryEndpoint } from "../endpoint/endpoint-client.ts"; +import { saveScrapePictureCandidate } from "../db/scrape-image-store.ts"; import { openInBrowser, scrapePage, searchInBrowser } from "./browser-adapter.ts"; import { prepareManufacturerCatalogSqls, @@ -247,6 +248,10 @@ export async function getProductPictures( }; } +export async function saveProductPictureCandidate(config, picture) { + return saveScrapePictureCandidate(config, picture); +} + export async function openProductSource(config, { manufacturerId, sourceKey, url }) { const sourceUrl = normalizeSourceUrl(url); const mapping = await listMappingSources(config, { manufacturerId }); diff --git a/src/tailwind.css b/src/tailwind.css index 5fba72b..5a1dab2 100644 --- a/src/tailwind.css +++ b/src/tailwind.css @@ -227,9 +227,15 @@ .source-status, .source-row-action { @apply shrink-0 rounded px-1 text-[10px]; } .source-status.is-ok { @apply bg-emerald-100 text-emerald-700; } .source-status.is-muted { @apply bg-slate-200 text-slate-600; } - .picture-candidate { @apply grid grid-cols-[72px_minmax(0,1fr)] items-center gap-2 border-b border-slate-200 p-1 text-[10px]; } - .picture-candidate img { @apply h-16 w-[72px] object-contain; } - .picture-candidate span { @apply min-w-0 truncate; } + .picture-candidate { @apply grid grid-cols-[72px_minmax(0,1fr)_auto] items-center gap-2 border-b border-slate-200 p-2 text-[10px]; } + .picture-candidate-preview { @apply flex h-16 w-[72px] items-center justify-center bg-white; } + .picture-candidate img { @apply max-h-16 w-[72px] object-contain; } + .picture-candidate-meta { @apply grid min-w-0 gap-0.5; } + .picture-candidate-title { @apply min-w-0 truncate font-semibold text-slate-800; } + .picture-candidate-dimensions, + .picture-candidate-target, + .picture-candidate-duplicate { @apply min-w-0 truncate text-slate-500; } + .picture-candidate-save { @apply min-w-[76px]; } @media (max-width: 980px) { .catalog-screen { @apply flex flex-col pl-0; } .catalog-screen.sidebar-collapsed { @apply pl-0; } @@ -358,5 +364,10 @@ html.dark .source-row { @apply border-slate-700 bg-slate-900 text-slate-100 hover:bg-slate-800; } html.dark .suggested-row, html.dark .picture-candidate { @apply border-slate-700; } + html.dark .picture-candidate-preview { @apply bg-slate-800; } + html.dark .picture-candidate-title { @apply text-slate-100; } + html.dark .picture-candidate-dimensions, + html.dark .picture-candidate-target, + html.dark .picture-candidate-duplicate { @apply text-slate-400; } html.dark .suggested-row:nth-child(even) { @apply bg-slate-800; } } diff --git a/tests/ui/catalog-maker.smoke.spec.ts b/tests/ui/catalog-maker.smoke.spec.ts index 79c73fc..790d3d7 100644 --- a/tests/ui/catalog-maker.smoke.spec.ts +++ b/tests/ui/catalog-maker.smoke.spec.ts @@ -138,6 +138,89 @@ test("main workspace keeps a bottom horizontal scrollbar for wide data", async ( expect(scrollState.scrollWidth).toBeGreaterThan(scrollState.clientWidth); }); +test("picture candidates show source, dimensions, target assignment and save action", async ({ page }) => { + await page.route("**/api/manufacturers", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ items: [{ id: 13, name: "La Sportiva" }] }), + }); + }); + await page.route("**/api/languages", async (route) => { + await route.fulfill({ contentType: "application/json", body: JSON.stringify({ items: [] }) }); + }); + await page.route("**/api/catalog-maker/mapping-sources**", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + mapped: true, + items: [{ key: "hudy", name: "Hudy", enabled: true, health: "ok" }], + }), + }); + }); + await page.route("**/api/catalog-maker/product**", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + product: { + idProductCatalog: 135520, + idProduct: 110927, + idManufacturer: 13, + codeManufacture: "LAS", + supplierReference: "ZFHS099", + name: "Aequilibrium Trek Woman GTX", + position: 1, + total: 380, + }, + combinations: [ + { + idProductCatalog: 110923, + status: "SS25/Boty", + color: "Carbon/Malibu Blue", + combination: "36", + ean13: "8058428191284", + supplierStock: 1, + }, + ], + }), + }); + }); + await page.route("**/api/catalog-maker/get-pictures**", async (route) => { + await route.fulfill({ + contentType: "application/json", + body: JSON.stringify({ + items: [ + { + source: { key: "hudy", name: "Hudy", type: "supplier" }, + searchUrl: "https://www.hudy.cz/vyhledavani?q=8058428191284", + candidates: [ + { + rawTitle: "Aequilibrium Trek Woman GTX", + url: "https://www.hudy.cz/product", + pictures: [ + "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='64'%3E%3Crect width='80' height='64' fill='red'/%3E%3C/svg%3E", + ], + }, + ], + }, + ], + }), + }); + }); + + await page.addInitScript(() => { + localStorage.setItem("catalog-maker:selected-manufacturer", "13"); + }); + await page.goto("/"); + await page.waitForFunction(() => document.querySelector("#manufacturerSelect")?.value === "13"); + await page.locator("#getPicturesButton").click(); + + await expect(page.locator("#sourceModal")).toBeVisible(); + await expect(page.locator(".picture-candidate-title")).toHaveText("Hudy picture #1"); + await expect(page.locator(".picture-candidate-dimensions")).toContainText("Size:"); + await expect(page.locator(".picture-candidate-target")).toContainText("Target: color Carbon/Malibu Blue"); + await expect(page.locator(".picture-candidate-save")).toHaveText("Save to DB"); +}); + test("uses one shared height for the top panel headers", async ({ page }) => { await page.goto("/"); const heights = await page.locator("#sidebarDrawer .sidebar-drawer-header, .secondary-panel .column-header, .main-workspace .column-header").evaluateAll((elements) => elements.map((element) => Math.round(element.getBoundingClientRect().height)));