{"product_id":"druckvorlage-fur-textilspannkederrahmen","title":"Druckvorlage für Textilspannkederrahmen","description":"\n{% comment %}\n  In Shopify als Benutzerdefiniertes Liquid einfügen.\n  Optional: vis24_system_name und vis24_order_url pro Produktvorlage setzen.\n{% endcomment %}\n{% assign vis24_system_name = 'Textildisplay' %}\n{% assign vis24_order_url = '' %}\n\n\u003cdiv class=\"vis24-textil-pdf\" data-vis24-textil-pdf data-system=\"{{ vis24_system_name | escape }}\" data-order-url=\"{{ vis24_order_url | escape }}\"\u003e\n  \u003cdetails class=\"cc-accordion-item\" open\u003e\n    \u003csummary class=\"cc-accordion-item__title\"\u003e\n      \u003ch3\u003ePDF-Druckvorlage für {{ vis24_system_name | escape }} erstellen\u003c\/h3\u003e\n    \u003c\/summary\u003e\n    \u003cdiv class=\"cc-accordion-item__panel\"\u003e\n      \u003cdiv class=\"vis24-textil-pdf__box\"\u003e\n        \u003cdiv class=\"vis24-textil-pdf__fields\"\u003e\n          \u003clabel\u003eAufbau\n            \u003cselect data-layout\u003e\n              \u003coption value=\"single\"\u003eEinzelner Rahmen\u003c\/option\u003e\n              \u003coption value=\"row\"\u003eMehrere Rahmen nebeneinander\u003c\/option\u003e\n            \u003c\/select\u003e\n          \u003c\/label\u003e\n          \u003clabel data-count-field hidden\u003eAnzahl Rahmen\u003cselect data-count\u003e\u003c\/select\u003e\u003c\/label\u003e\n          \u003clabel\u003eMaßstab\n            \u003cselect data-scale\u003e\n              \u003coption value=\"1\"\u003e1:1 – Originalgröße\u003c\/option\u003e\n              \u003coption value=\"2\"\u003e1:2\u003c\/option\u003e\n              \u003coption value=\"10\" selected\u003e1:10\u003c\/option\u003e\n            \u003c\/select\u003e\n          \u003c\/label\u003e\n        \u003c\/div\u003e\n        \u003cdiv data-frames\u003e\u003c\/div\u003e\n\n        \u003cfigure class=\"vis24-textil-pdf__preview\" data-preview hidden\u003e\n          \u003cfigcaption\u003e\u003cstrong\u003eVorschau von vorne\u003c\/strong\u003e\u003c\/figcaption\u003e\n          \u003cdiv class=\"vis24-textil-pdf__drawing\" data-drawing aria-hidden=\"true\"\u003e\u003c\/div\u003e\n          \u003cp class=\"vis24-textil-pdf__note\"\u003eDie Rahmen stehen unten bündig. Mit den Pfeiltasten unter den Rahmenmaßen ändern Sie die Reihenfolge von links nach rechts.\u003c\/p\u003e\n        \u003c\/figure\u003e\n\n        \u003cp class=\"vis24-textil-pdf__format\" data-format aria-live=\"polite\"\u003e\u003c\/p\u003e\n        \u003cdiv data-table hidden\u003e\n          \u003ctable aria-label=\"Produktionsmaße je Rahmen im Originalformat\"\u003e\n            \u003cthead\u003e\n              \u003ctr\u003e\n                \u003cth scope=\"col\"\u003eNr.\u003c\/th\u003e\n                \u003cth scope=\"col\"\u003eEndformat\u003c\/th\u003e\n                \u003cth scope=\"col\"\u003eMit Beschnitt\u003c\/th\u003e\n                \u003cth scope=\"col\"\u003eSicherheits\u003cwbr\u003ebereich\u003c\/wbr\u003e\n\u003c\/th\u003e\n              \u003c\/tr\u003e\n            \u003c\/thead\u003e\n            \u003ctbody data-sizes\u003e\u003c\/tbody\u003e\n          \u003c\/table\u003e\n        \u003c\/div\u003e\n        \u003cp class=\"vis24-textil-pdf__note\" data-status\u003e\u003c\/p\u003e\n        \u003cp class=\"vis24-textil-pdf__note\" data-announcement aria-live=\"polite\"\u003e\u003c\/p\u003e\n        \u003cp class=\"vis24-textil-pdf__error\" data-warning aria-live=\"polite\" hidden\u003e\u003c\/p\u003e\n        \u003cp class=\"vis24-textil-pdf__error\" data-error role=\"alert\" hidden\u003e\u003c\/p\u003e\n        \u003cbutton class=\"vis24-textil-pdf__download\" type=\"button\" data-download disabled\u003ePDF-Druckvorlagen herunterladen\u003c\/button\u003e\n        \u003cp class=\"vis24-textil-pdf__note\"\u003eEntfernen Sie die Hilfslinien und Beschriftungen vor dem Export Ihrer Druckdateien.\u003c\/p\u003e\n        {% if vis24_order_url != blank %}\n          \u003cdiv class=\"vis24-textil-pdf__order\"\u003e\n            \u003cbutton class=\"vis24-textil-pdf__download\" type=\"button\" data-order disabled\u003eErsatzdrucke für diese Rahmen bestellen\u003c\/button\u003e\n            \u003col data-order-links hidden\u003e\u003c\/ol\u003e\n          \u003c\/div\u003e\n        {% endif %}\n      \u003c\/div\u003e\n    \u003c\/div\u003e\n  \u003c\/details\u003e\n\u003c\/div\u003e\n\n\u003cstyle\u003e\n  .vis24-textil-pdf { margin: 20px 0; min-width: 0; }\n  .vis24-textil-pdf [hidden] { display: none !important; }\n  .vis24-textil-pdf summary { padding: .75rem 0; cursor: pointer; }\n  .vis24-textil-pdf h3 { margin: 0; font-size: 1.25rem; }\n  .vis24-textil-pdf__box {\n    min-width: 0; padding: clamp(10px, 3vw, 18px); border: 1px solid #ddd;\n    border-radius: 12px; background: #eee; color: #222;\n  }\n  .vis24-textil-pdf__fields,\n  .vis24-textil-pdf__dimensions { display: grid; gap: 12px; }\n  .vis24-textil-pdf__fields {\n    grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr));\n    margin-bottom: 14px;\n  }\n  .vis24-textil-pdf__dimensions { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n  .vis24-textil-pdf__frame { min-width: 0; margin: 0 0 14px; padding: 0; border: 0; }\n  .vis24-textil-pdf__frame legend { margin-bottom: 5px; padding: 0; font-weight: 700; }\n  .vis24-textil-pdf label { min-width: 0; font-weight: 600; }\n  .vis24-textil-pdf select,\n  .vis24-textil-pdf input {\n    box-sizing: border-box; display: block; width: 100%; min-width: 0;\n    min-height: 44px; margin-top: 5px; padding: 10px; border: 1px solid #ccc;\n    border-radius: 8px; background: #fff; color: #222; font-size: 16px;\n  }\n  .vis24-textil-pdf__move { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }\n  .vis24-textil-pdf__move button {\n    min-height: 44px; padding: 7px 12px; border: 1px solid #999;\n    border-radius: 8px; background: #fff; color: #222; cursor: pointer;\n  }\n  .vis24-textil-pdf__preview {\n    margin: 18px 0; padding: 14px; border: 1px solid #ccc;\n    border-radius: 8px; background: #fff;\n  }\n  .vis24-textil-pdf__drawing { position: relative; margin: 12px auto 0; box-shadow: 0 2px #24487b; }\n  .vis24-textil-pdf__panel {\n    position: absolute; bottom: 0; box-sizing: border-box; display: grid;\n    place-items: center; overflow: hidden; border: 1px solid #24487b;\n    background: #dbe8fb; color: #173763; font-size: clamp(9px, 2vw, 14px); font-weight: 700;\n  }\n  .vis24-textil-pdf__panel:nth-child(even) { background: #edf3fc; }\n  .vis24-textil-pdf__format,\n  .vis24-textil-pdf__error { color: #b00020; font-weight: 700; }\n  .vis24-textil-pdf__format { margin: 16px 0 10px; font-size: 22px; }\n  .vis24-textil-pdf__error { margin: 12px 0; font-size: 17px; }\n  .vis24-textil-pdf table {\n    width: 100%; max-width: 100%; min-width: 0; margin: 0;\n    table-layout: fixed; border-collapse: collapse; font-size: clamp(12px, 2vw, 14px);\n  }\n  .vis24-textil-pdf th,\n  .vis24-textil-pdf td {\n    padding: 6px 4px; border-bottom: 1px solid #d2d2d2; text-align: left;\n    vertical-align: top; white-space: normal; overflow-wrap: anywhere;\n  }\n  .vis24-textil-pdf thead th:first-child { width: 12%; }\n  .vis24-textil-pdf__note { margin: 12px 0; color: #555; font-size: 14px; }\n  .vis24-textil-pdf [data-announcement]:empty,\n  .vis24-textil-pdf [data-status]:empty { margin: 0; }\n  .vis24-textil-pdf__download {\n    box-sizing: border-box; width: 100%; padding: 13px 20px; border: 1px solid transparent;\n    border-radius: 999px; background: linear-gradient(90deg, #ff5a00, #e6007e);\n    color: #fff; font-weight: 700; cursor: pointer;\n  }\n  .vis24-textil-pdf button:disabled { opacity: .55; cursor: not-allowed; }\n  .vis24-textil-pdf__order { margin-top: 16px; }\n  .vis24-textil-pdf__order a { color: #a0001c; text-decoration: underline; }\n\u003c\/style\u003e\n\n\u003cscript\u003e\n(function () {\n  \"use strict\";\n\n  const BLEED = 20;\n  const SAFETY = 10;\n  const MAX_PDF_SIDE = 5080;\n  const SCALES = [1, 2, 10];\n  const POINTS_PER_MM = 72 \/ 25.4;\n  const number = new Intl.NumberFormat(\"de-DE\", { maximumFractionDigits: 1, useGrouping: false });\n\n  function size(width, height) {\n    return number.format(width) + \" × \" + number.format(height) + \" mm\";\n  }\n\n  function parseMm(value) {\n    const raw = value.trim();\n    return \/^\\d+$\/.test(raw) ? Number(raw) : NaN;\n  }\n\n  function show(element, message) {\n    element.textContent = message;\n    element.hidden = !message;\n  }\n\n  function boundsOf(frames) {\n    return {\n      width: frames.reduce((sum, frame) =\u003e sum + frame.width, 0),\n      height: Math.max(...frames.map(frame =\u003e frame.height))\n    };\n  }\n\n  function fitsPdf(bounds, scale) {\n    return (Math.max(bounds.width, bounds.height) + 2 * BLEED) \/ scale \u003c= MAX_PDF_SIDE;\n  }\n\n  function hasHeightPeak(frames) {\n    return frames.some((frame, index) =\u003e\n      index \u003e 0 \u0026\u0026 index \u003c frames.length - 1 \u0026\u0026\n      frame.height \u003e frames[index - 1].height \u0026\u0026\n      frame.height \u003e frames[index + 1].height\n    );\n  }\n\n  function loadPdfLib() {\n    if (window.PDFLib) return Promise.resolve(window.PDFLib);\n    if (!window.vis24TextilPdfLibrary) {\n      window.vis24TextilPdfLibrary = new Promise((resolve, reject) =\u003e {\n        const script = document.createElement(\"script\");\n        script.src = \"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf-lib\/1.17.1\/pdf-lib.min.js\";\n        script.onload = () =\u003e window.PDFLib ? resolve(window.PDFLib) : reject();\n        script.onerror = () =\u003e { script.remove(); reject(); };\n        document.head.appendChild(script);\n      }).catch(() =\u003e {\n        window.vis24TextilPdfLibrary = null;\n        throw new Error(\"Die PDF-Bibliothek konnte nicht geladen werden. Bitte versuchen Sie es erneut.\");\n      });\n    }\n    return window.vis24TextilPdfLibrary;\n  }\n\n  function drawCentered(page, font, text, sizePt, x, y, maxWidth, color) {\n    const width = font.widthOfTextAtSize(text, sizePt);\n    const fitted = sizePt * Math.min(1, maxWidth \/ width);\n    page.drawText(text, {\n      x: x - font.widthOfTextAtSize(text, fitted) \/ 2, y, size: fitted, font, color\n    });\n  }\n\n  function addOverview(pdf, font, config, system, colors) {\n    const page = pdf.addPage([842, 595]);\n    const { width, height } = config.bounds;\n    const diagramScale = Math.min(746 \/ width, 245 \/ height);\n    const baseline = 170;\n    let left = (842 - width * diagramScale) \/ 2;\n\n    const lines = [\n      [system + \" - Übersicht der Rahmen\", 24, 540],\n      [\"Gesamtbreite × maximale Höhe: \" + size(width, height), 14, 510],\n      [\"Vorderansicht von links nach rechts. Rahmen unten bündig.\", 11, 486],\n      [\"Einzelvorlagen im Maßstab 1:\" + config.scale + \".\", 11, 465],\n      [\"Jeder Rahmen erhält umlaufend 20 mm Beschnitt im Originalformat.\", 11, 125],\n      [\"An Rahmenstößen das Motiv in den Beschnitt der Einzelvorlage weiterführen.\", 11, 105]\n    ];\n    lines.forEach(([text, fontSize, y]) =\u003e\n      drawCentered(page, font, text, fontSize, 421, y, 746, colors.text)\n    );\n\n    config.frames.forEach((frame, index) =\u003e {\n      const w = frame.width * diagramScale;\n      const h = frame.height * diagramScale;\n      page.drawRectangle({\n        x: left, y: baseline, width: w, height: h,\n        color: colors.fill, borderColor: colors.blue, borderWidth: 1\n      });\n      drawCentered(page, font, \"Rahmen \" + (index + 1), Math.min(13, h \/ 5),\n        left + w \/ 2, baseline + h * .53, w * .88, colors.text);\n      drawCentered(page, font, size(frame.width, frame.height), Math.min(11, h \/ 6),\n        left + w \/ 2, baseline + h * .3, w * .88, colors.text);\n      left += w;\n    });\n  }\n\n  function addTemplate(pdf, font, frames, scale, title, colors) {\n    const { width, height } = boundsOf(frames);\n    const points = POINTS_PER_MM \/ scale;\n    const bleed = BLEED * points;\n    const safety = SAFETY * points;\n    const pageWidth = (width + 2 * BLEED) * points;\n    const pageHeight = (height + 2 * BLEED) * points;\n    const line = 1.2 * points;\n    const page = pdf.addPage([pageWidth, pageHeight]);\n    const combined = frames.length \u003e 1;\n\n    page.setBleedBox(0, 0, pageWidth, pageHeight);\n    page.setTrimBox(bleed, bleed, width * points, height * points);\n    page.drawRectangle({\n      x: line \/ 2, y: line \/ 2, width: pageWidth - line, height: pageHeight - line,\n      borderColor: colors.red, borderWidth: line\n    });\n\n    const firstWidth = frames[0].width * points;\n    const firstHeight = frames[0].height * points;\n    const titleSize = Math.min(firstWidth, firstHeight) * .065;\n    const detailSize = titleSize * .55;\n    const gap = titleSize * 1.25;\n    let left = bleed;\n\n    frames.forEach((frame, index) =\u003e {\n      const w = frame.width * points;\n      const h = frame.height * points;\n      page.drawRectangle({\n        x: left, y: bleed, width: w, height: h, borderColor: colors.blue, borderWidth: line\n      });\n      page.drawRectangle({\n        x: left + safety, y: bleed + safety, width: w - 2 * safety, height: h - 2 * safety,\n        borderColor: colors.green, borderWidth: line\n      });\n      if (combined) {\n        const labelSize = Math.min(titleSize, h * .08);\n        drawCentered(page, font, \"Rahmen \" + (index + 1), labelSize,\n          left + w \/ 2, bleed + h * .35, w * .85, colors.text);\n        drawCentered(page, font, size(frame.width, frame.height), labelSize * .55,\n          left + w \/ 2, bleed + h * .35 - labelSize * 1.25, w * .85, colors.text);\n      }\n      left += w;\n    });\n\n    let join = bleed;\n    frames.slice(0, -1).forEach((frame, index) =\u003e {\n      join += frame.width * points;\n      page.drawLine({\n        start: { x: join, y: bleed },\n        end: { x: join, y: bleed + Math.min(frame.height, frames[index + 1].height) * points },\n        thickness: line * 1.5, dashArray: [5.5 * points, 3.5 * points], color: colors.split\n      });\n    });\n\n    const labels = [\n      [title, titleSize, colors.text],\n      [\"Maßstab 1:\" + scale + \" | Maße im Originalformat\", detailSize, colors.text],\n      [combined ? \"BLAU - Sichtfläche je Rahmen\" : \"BLAU - Endformat: \" + size(width, height), detailSize, colors.blue],\n      [\"ROT - Dateiformat inkl. Beschnitt: \" + size(width + 2 * BLEED, height + 2 * BLEED), detailSize, colors.red],\n      [combined ? \"GRÜN - Sicherheitsbereich je Rahmen\" :\n        \"GRÜN - Sicherheitsbereich: \" + size(width - 2 * SAFETY, height - 2 * SAFETY), detailSize, colors.green]\n    ];\n    if (combined) labels.push([\"VIOLETT GESTRICHELT - Rahmenstoß\", detailSize, colors.split]);\n\n    const headingY = combined ? bleed + firstHeight * .85 : pageHeight \/ 2 + 2 * gap;\n    labels.forEach(([text, fontSize, color], index) =\u003e {\n      drawCentered(page, font, text, fontSize, bleed + firstWidth \/ 2,\n        headingY - index * gap, (firstWidth - 2 * safety) * .88, color);\n    });\n  }\n\n  function savePdf(bytes, filename) {\n    const url = URL.createObjectURL(new Blob([bytes], { type: \"application\/pdf\" }));\n    const link = document.createElement(\"a\");\n    link.href = url;\n    link.download = filename;\n    document.body.appendChild(link);\n    link.click();\n    link.remove();\n    window.setTimeout(() =\u003e URL.revokeObjectURL(url), 30000);\n  }\n\n  function init(root) {\n    if (root.dataset.initialized) return;\n    root.dataset.initialized = \"true\";\n\n    const find = selector =\u003e root.querySelector(selector);\n    const system = root.dataset.system || \"Textildisplay\";\n    \/\/ Helvetica in pdf-lib verwendet WinAnsi. PDF-Systemnamen auf unterstützte Zeichen begrenzen.\n    const pdfSystem = system.replace(\/[–—]\/g, \"-\").replace(\/[^\\u0020-\\u007e\\u00a0-\\u00ff]\/g, \"\");\n    const layout = find(\"[data-layout]\");\n    const count = find(\"[data-count]\");\n    const scale = find(\"[data-scale]\");\n    const framesBox = find(\"[data-frames]\");\n    const preview = find(\"[data-preview]\");\n    const drawing = find(\"[data-drawing]\");\n    const table = find(\"[data-table]\");\n    const sizes = find(\"[data-sizes]\");\n    const status = find(\"[data-status]\");\n    const warning = find(\"[data-warning]\");\n    const error = find(\"[data-error]\");\n    const download = find(\"[data-download]\");\n    const announcement = find(\"[data-announcement]\");\n    const order = find(\"[data-order]\");\n    const orderLinks = find(\"[data-order-links]\");\n    const frames = [{ width: \"\", height: \"\" }];\n    let generating = false;\n\n    for (let quantity = 2; quantity \u003c= 12; quantity += 1) {\n      count.add(new Option(String(quantity), String(quantity)));\n    }\n\n    function readConfig() {\n      const multiple = layout.value === \"row\";\n      const quantity = multiple ? Number(count.value) : 1;\n      const activeFrames = frames.slice(0, quantity).map(frame =\u003e ({\n        width: parseMm(frame.width), height: parseMm(frame.height)\n      }));\n      const valid = activeFrames.every(frame =\u003e\n        [frame.width, frame.height].every(value =\u003e Number.isSafeInteger(value) \u0026\u0026 value \u003e 2 * SAFETY)\n      );\n      const selectedScale = Number(scale.value);\n      const bounds = valid ? boundsOf(activeFrames) : null;\n      return {\n        frames: activeFrames, multiple, valid, bounds, scale: selectedScale,\n        peak: valid \u0026\u0026 hasHeightPeak(activeFrames),\n        totalFits: valid \u0026\u0026 fitsPdf(bounds, selectedScale),\n        individualFits: valid \u0026\u0026 activeFrames.every(frame =\u003e fitsPdf(frame, selectedScale))\n      };\n    }\n\n    function makeField(labelText, key, value, placeholder) {\n      const label = document.createElement(\"label\");\n      const field = document.createElement(\"input\");\n      label.textContent = labelText;\n      field.type = \"text\";\n      field.inputMode = \"numeric\";\n      field.autocomplete = \"off\";\n      field.dataset[key] = \"\";\n      field.placeholder = placeholder;\n      field.value = value;\n      label.appendChild(field);\n      return label;\n    }\n\n    function makeMoveButton(index, direction, disabled) {\n      const button = document.createElement(\"button\");\n      const left = direction \u003c 0;\n      button.type = \"button\";\n      button.textContent = left ? \"← Nach links\" : \"Nach rechts →\";\n      button.dataset.move = String(direction);\n      button.setAttribute(\"aria-label\", \"Rahmen \" + (index + 1) + (left ? \" nach links\" : \" nach rechts\"));\n      button.disabled = disabled;\n      return button;\n    }\n\n    function renderFrames() {\n      const multiple = layout.value === \"row\";\n      const quantity = multiple ? Number(count.value) : 1;\n      while (frames.length \u003c quantity) frames.push({ width: \"\", height: frames[0].height });\n      find(\"[data-count-field]\").hidden = !multiple;\n      framesBox.replaceChildren();\n\n      frames.slice(0, quantity).forEach((frame, index) =\u003e {\n        const fieldset = document.createElement(\"fieldset\");\n        const legend = document.createElement(\"legend\");\n        const fields = document.createElement(\"div\");\n        fieldset.className = \"vis24-textil-pdf__frame\";\n        fieldset.dataset.frameIndex = String(index);\n        legend.textContent = multiple ? \"Rahmen \" + (index + 1) : \"Rahmenmaße\";\n        fields.className = \"vis24-textil-pdf__dimensions\";\n        fields.append(\n          makeField(\"Breite (mm)\", \"width\", frame.width, \"z. B. 1000\"),\n          makeField(\"Höhe (mm)\", \"height\", frame.height, \"z. B. 2000\")\n        );\n        fieldset.append(legend, fields);\n        if (multiple) {\n          const controls = document.createElement(\"div\");\n          controls.className = \"vis24-textil-pdf__move\";\n          controls.append(makeMoveButton(index, -1, index === 0),\n            makeMoveButton(index, 1, index === quantity - 1));\n          fieldset.appendChild(controls);\n        }\n        framesBox.appendChild(fieldset);\n      });\n    }\n\n    function renderPreview(config) {\n      preview.hidden = !config.valid || !config.multiple;\n      drawing.replaceChildren();\n      if (preview.hidden) return;\n\n      const { width, height } = config.bounds;\n      drawing.style.width = \"min(100%, \" + (240 * width \/ height) + \"px)\";\n      drawing.style.aspectRatio = width + \" \/ \" + height;\n      let left = 0;\n\n      config.frames.forEach((frame, index) =\u003e {\n        const panel = document.createElement(\"div\");\n        panel.className = \"vis24-textil-pdf__panel\";\n        panel.style.left = (100 * left \/ width) + \"%\";\n        panel.style.width = (100 * frame.width \/ width) + \"%\";\n        panel.style.height = (100 * frame.height \/ height) + \"%\";\n        panel.textContent = String(index + 1);\n        panel.title = \"Rahmen \" + (index + 1) + \" · \" + size(frame.width, frame.height);\n        drawing.appendChild(panel);\n        left += frame.width;\n      });\n    }\n\n    function update() {\n      const config = readConfig();\n      const format = find(\"[data-format]\");\n      download.disabled = generating || !config.individualFits;\n      if (order) {\n        order.disabled = !config.valid;\n        orderLinks.hidden = true;\n        orderLinks.replaceChildren();\n      }\n      renderPreview(config);\n      table.hidden = !config.valid;\n      sizes.replaceChildren();\n      format.textContent = \"\";\n      status.textContent = \"\";\n      show(warning, \"\");\n\n      if (!config.valid) {\n        status.textContent = \"Bitte die sichtbare Breite und Höhe aller Rahmen in ganzen mm eingeben (jeweils über 20 mm).\";\n        return;\n      }\n\n      const { width, height } = config.bounds;\n      const mixed = config.frames.some(frame =\u003e frame.height !== height);\n      format.textContent = mixed\n        ? \"Gesamtbreite \" + number.format(width) + \" mm · maximale Höhe \" + number.format(height) + \" mm\"\n        : \"Sichtfläche \" + size(width, height);\n\n      config.frames.forEach((frame, index) =\u003e {\n        const row = sizes.insertRow();\n        const heading = document.createElement(\"th\");\n        heading.scope = \"row\";\n        heading.textContent = String(index + 1);\n        row.appendChild(heading);\n        row.insertCell().textContent = size(frame.width, frame.height);\n        row.insertCell().textContent = size(frame.width + 2 * BLEED, frame.height + 2 * BLEED);\n        row.insertCell().textContent = size(frame.width - 2 * SAFETY, frame.height - 2 * SAFETY);\n      });\n\n      status.textContent = \"Maßstab 1:\" + config.scale +\n        \" · \" + number.format(BLEED \/ config.scale) + \" mm Beschnitt und \" +\n        number.format(SAFETY \/ config.scale) + \" mm Sicherheitsabstand je Kante in der Einzelvorlage.\";\n\n      if (!config.individualFits) {\n        const next = SCALES.find(candidate =\u003e config.frames.every(frame =\u003e fitsPdf(frame, candidate)));\n        show(warning, next\n          ? \"Mindestens eine Einzelvorlage überschreitet die PDF-Seitengröße. Bitte Maßstab 1:\" + next + \" wählen.\"\n          : \"Mindestens eine Einzelvorlage überschreitet auch bei 1:10 die PDF-Seitengröße.\");\n      } else if (config.peak) {\n        show(warning, \"Ein Rahmen ist höher als seine beiden Nachbarn. Die PDF enthält die Übersicht und die nummerierten Einzelvorlagen.\");\n      } else if (config.multiple \u0026\u0026 !config.totalFits) {\n        const next = SCALES.find(candidate =\u003e fitsPdf(config.bounds, candidate));\n        show(warning, \"Die Gesamtfläche passt bei diesem Maßstab nicht auf eine PDF-Seite. \" +\n          \"Die PDF enthält die Übersicht und die Einzelvorlagen.\" +\n          (next ? \" Für die Gesamtfläche wählen Sie bitte 1:\" + next + \".\" : \"\"));\n      }\n    }\n\n    download.addEventListener(\"click\", async () =\u003e {\n      const config = readConfig();\n      if (generating || !config.individualFits) return;\n      show(error, \"\");\n      generating = true;\n      download.disabled = true;\n\n      try {\n        const { PDFDocument, StandardFonts, rgb } = await loadPdfLib();\n        const pdf = await PDFDocument.create();\n        const font = await pdf.embedFont(StandardFonts.HelveticaBold);\n        const colors = {\n          text: rgb(.13, .13, .13), red: rgb(.75, .02, .12), blue: rgb(.02, .23, .75),\n          green: rgb(0, .48, .18), split: rgb(.48, .12, .68), fill: rgb(.92, .95, 1)\n        };\n        pdf.setTitle(\"VIS24 \" + system + \" - Druckvorlagen 1:\" + config.scale);\n        if (config.multiple) addOverview(pdf, font, config, pdfSystem, colors);\n        config.frames.forEach((frame, index) =\u003e {\n          addTemplate(pdf, font, [frame], config.scale,\n            pdfSystem + \" - Rahmen \" + (index + 1) + \" von \" + config.frames.length, colors);\n        });\n        if (config.multiple \u0026\u0026 !config.peak \u0026\u0026 config.totalFits) {\n          addTemplate(pdf, font, config.frames, config.scale, pdfSystem + \" - Gesamtfläche\", colors);\n        }\n        const slug = system.replace(\/[^a-zA-Z0-9]+\/g, \"-\").replace(\/^-|-$\/g, \"\") || \"Textildisplay\";\n        savePdf(await pdf.save(), \"VIS24_\" + slug + \"_Druckvorlagen_\" +\n          config.frames.length + \"-Rahmen_1zu\" + config.scale + \".pdf\");\n      } catch (exception) {\n        console.error(\"VIS24 Textildisplay PDF\", exception);\n        show(error, exception.message?.startsWith(\"Die PDF-Bibliothek\")\n          ? exception.message\n          : \"Die PDF konnte nicht erstellt werden. Bitte versuchen Sie es erneut.\");\n      } finally {\n        generating = false;\n        download.disabled = !readConfig().individualFits;\n      }\n    });\n\n    order?.addEventListener(\"click\", () =\u003e {\n      const config = readConfig();\n      if (!config.valid) return;\n      orderLinks.replaceChildren();\n      config.frames.forEach((frame, index) =\u003e {\n        const item = document.createElement(\"li\");\n        const link = document.createElement(\"a\");\n        link.href = root.dataset.orderUrl;\n        link.textContent = \"Rahmen \" + (index + 1) + \" · \" + size(frame.width, frame.height) +\n          \" – Ersatzdruck öffnen\";\n        item.appendChild(link);\n        orderLinks.appendChild(item);\n      });\n      orderLinks.hidden = false;\n    });\n\n    framesBox.addEventListener(\"input\", event =\u003e {\n      const field = event.target;\n      if (!field.matches(\"[data-width], [data-height]\")) return;\n      const index = Number(field.closest(\"[data-frame-index]\").dataset.frameIndex);\n      const key = field.matches(\"[data-width]\") ? \"width\" : \"height\";\n      frames[index][key] = field.value;\n      announcement.textContent = \"\";\n      show(error, \"\");\n      update();\n    });\n\n    root.addEventListener(\"change\", event =\u003e {\n      if (event.target === layout || event.target === count) {\n        renderFrames();\n      } else if (event.target !== scale) {\n        return;\n      }\n      announcement.textContent = \"\";\n      show(error, \"\");\n      update();\n    });\n\n    framesBox.addEventListener(\"click\", event =\u003e {\n      const button = event.target.closest(\"[data-move]\");\n      if (!button || button.disabled) return;\n      const index = Number(button.closest(\"[data-frame-index]\").dataset.frameIndex);\n      const target = index + Number(button.dataset.move);\n      [frames[index], frames[target]] = [frames[target], frames[index]];\n      renderFrames();\n      show(error, \"\");\n      update();\n      announcement.textContent = \"Rahmen steht jetzt an Position \" + (target + 1) + \".\";\n      const row = framesBox.querySelector('[data-frame-index=\"' + target + '\"]');\n      const same = row.querySelector('[data-move=\"' + button.dataset.move + '\"]');\n      (same.disabled ? row.querySelector(\"[data-move]:not(:disabled)\") : same)?.focus();\n    });\n\n    renderFrames();\n    update();\n  }\n\n  const selector = \"[data-vis24-textil-pdf]\";\n  document.querySelectorAll(selector).forEach(init);\n  if (!window.vis24TextilPdfSectionHandler) {\n    window.vis24TextilPdfSectionHandler = true;\n    document.addEventListener(\"shopify:section:load\", event =\u003e {\n      event.target.querySelectorAll(selector).forEach(init);\n    });\n  }\n})();\n\u003c\/script\u003e\n\n\n\n","brand":"VIS24","offers":[{"title":"Default Title","offer_id":57563934621961,"sku":null,"price":0.0,"currency_code":"EUR","in_stock":false}],"url":"https:\/\/vis24.de\/products\/druckvorlage-fur-textilspannkederrahmen","provider":"VIS24","version":"1.0","type":"link"}