{"id":18,"date":"2026-03-12T22:18:56","date_gmt":"2026-03-12T22:18:56","guid":{"rendered":"https:\/\/yotoimages.com\/?page_id=18"},"modified":"2026-03-13T17:19:12","modified_gmt":"2026-03-13T17:19:12","slug":"create","status":"publish","type":"page","link":"https:\/\/yotoimages.com\/fr\/create\/","title":{"rendered":"Create Yoto image"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"18\" class=\"elementor elementor-18\">\n\t\t\t\t<div class=\"elementor-element elementor-element-e4245be e-flex e-con-boxed e-con e-parent\" data-id=\"e4245be\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8696d21 elementor-widget elementor-widget-html\" data-id=\"8696d21\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<div id=\"yoto-card-creator-app\">\n  <style>\n    #yoto-card-creator-app{\n      --bg1:#0c1020;\n      --bg2:#131935;\n      --panel:#171b2e;\n      --panel2:#1f2440;\n      --text:#f3f5ff;\n      --muted:#aeb6d9;\n      --accent:#f0b224;\n      --accent2:#ffd36a;\n      --success:#68d391;\n      --border:#2f365d;\n      --radius:18px;\n      --shadow:0 12px 40px rgba(0,0,0,.22);\n      font-family:Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;\n      color:var(--text);\n    }\n\n    #yoto-card-creator-app *{\n      box-sizing:border-box;\n    }\n\n    #yoto-card-creator-app .yc-wrap{\n      max-width:1280px;\n      margin:0 auto;\n      padding:20px;\n      background:linear-gradient(180deg,var(--bg1),var(--bg2));\n      border-radius:24px;\n    }\n\n    #yoto-card-creator-app .yc-title{\n      margin:0 0 8px;\n      font-size:clamp(28px,4vw,40px);\n      line-height:1.05;\n    }\n\n    #yoto-card-creator-app .yc-subtitle{\n      margin:0 0 20px;\n      color:var(--muted);\n      line-height:1.5;\n      font-size:16px;\n    }\n\n    #yoto-card-creator-app .yc-grid{\n      display:grid;\n      grid-template-columns:380px 1fr;\n      gap:18px;\n    }\n\n    #yoto-card-creator-app .yc-panel{\n      background:rgba(23,27,46,.96);\n      border:1px solid var(--border);\n      border-radius:var(--radius);\n      padding:16px;\n      box-shadow:var(--shadow);\n    }\n\n    #yoto-card-creator-app .yc-panel h2{\n      margin:0 0 12px;\n      font-size:18px;\n    }\n\n    #yoto-card-creator-app .yc-controls{\n      display:grid;\n      gap:12px;\n    }\n\n    #yoto-card-creator-app label{\n      display:grid;\n      gap:6px;\n      color:var(--muted);\n      font-size:14px;\n    }\n\n    #yoto-card-creator-app input[type=\"text\"],\n    #yoto-card-creator-app input[type=\"file\"],\n    #yoto-card-creator-app input[type=\"range\"],\n    #yoto-card-creator-app input[type=\"color\"],\n    #yoto-card-creator-app select,\n    #yoto-card-creator-app button{\n      width:100%;\n    }\n\n    #yoto-card-creator-app input[type=\"text\"],\n    #yoto-card-creator-app input[type=\"file\"],\n    #yoto-card-creator-app select,\n    #yoto-card-creator-app button{\n      border-radius:12px;\n      border:1px solid var(--border);\n      background:var(--panel2);\n      color:var(--text);\n      padding:12px;\n      font-size:14px;\n    }\n\n    #yoto-card-creator-app button{\n      cursor:pointer;\n      font-weight:600;\n      transition:transform .08s ease, border-color .2s ease;\n    }\n\n    #yoto-card-creator-app button:hover{\n      border-color:#5d6db4;\n    }\n\n    #yoto-card-creator-app button:active{\n      transform:translateY(1px);\n    }\n\n    #yoto-card-creator-app .yc-btn-primary{\n      background:linear-gradient(180deg,var(--accent2),var(--accent));\n      color:#251600;\n      border:none;\n    }\n\n    #yoto-card-creator-app .yc-btn-success{\n      background:linear-gradient(180deg,#7ce4a4,#4eca79);\n      color:#092413;\n      border:none;\n    }\n\n    #yoto-card-creator-app .yc-dropzone{\n      border:1.5px dashed #5867a8;\n      border-radius:14px;\n      padding:16px;\n      text-align:center;\n      color:var(--muted);\n      background:#10152a;\n    }\n\n    #yoto-card-creator-app .yc-dropzone.drag{\n      border-color:var(--accent);\n      background:#171f39;\n      color:#fff;\n    }\n\n    #yoto-card-creator-app .yc-row-2{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:10px;\n    }\n\n    #yoto-card-creator-app .yc-row-3{\n      display:grid;\n      grid-template-columns:1fr 1fr 1fr;\n      gap:10px;\n    }\n\n    #yoto-card-creator-app .yc-help{\n      font-size:12px;\n      color:var(--muted);\n      line-height:1.5;\n    }\n\n    #yoto-card-creator-app .yc-preview-grid{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:18px;\n    }\n\n    #yoto-card-creator-app .yc-canvas-card{\n      background:#101426;\n      border:1px solid var(--border);\n      border-radius:16px;\n      padding:12px;\n    }\n\n    #yoto-card-creator-app .yc-canvas-title{\n      display:flex;\n      justify-content:space-between;\n      align-items:center;\n      gap:12px;\n      margin-bottom:10px;\n      color:var(--muted);\n      font-size:14px;\n    }\n\n    #yoto-card-creator-app canvas{\n      width:100%;\n      display:block;\n      border-radius:12px;\n      background:#0a0d18;\n      image-rendering:pixelated;\n      image-rendering:crisp-edges;\n    }\n\n    #yoto-card-creator-app .yc-stats{\n      display:grid;\n      grid-template-columns:repeat(3,1fr);\n      gap:10px;\n      margin-top:14px;\n    }\n\n    #yoto-card-creator-app .yc-stat{\n      background:#11162b;\n      border:1px solid var(--border);\n      border-radius:12px;\n      padding:10px;\n      color:var(--muted);\n      font-size:13px;\n    }\n\n    #yoto-card-creator-app .yc-stat strong{\n      display:block;\n      margin-top:4px;\n      font-size:18px;\n      color:#fff;\n    }\n\n    #yoto-card-creator-app .yc-footer{\n      margin-top:18px;\n      color:var(--muted);\n      font-size:13px;\n      text-align:center;\n    }\n\n    @media (max-width: 980px){\n      #yoto-card-creator-app .yc-grid{\n        grid-template-columns:1fr;\n      }\n      #yoto-card-creator-app .yc-preview-grid{\n        grid-template-columns:1fr;\n      }\n      #yoto-card-creator-app .yc-row-3{\n        grid-template-columns:1fr;\n      }\n    }\n  <\/style>\n\n  <div class=\"yc-wrap\">\n    <h1 id=\"ycTitle\" class=\"yc-title\">Create a Yoto Card<\/h1>\n    <p id=\"ycSubtitle\" class=\"yc-subtitle\">\n      Upload an image or take a photo, turn it into pixel art, add a title, and export both the icon and the printable card.\n    <\/p>\n\n    <div class=\"yc-grid\">\n      <div class=\"yc-panel\">\n        <h2>Settings<\/h2>\n\n        <div class=\"yc-controls\">\n          <div id=\"ycDropZone\" class=\"yc-dropzone\">\n            <span id=\"ycDropMain\">Drop your image here<\/span><br>\n            <span id=\"ycDropSub\" class=\"yc-help\">or use the upload button below<\/span>\n          <\/div>\n\n          <label>\n            <span id=\"ycUploadLabel\">Upload image \/ take photo<\/span>\n            <input id=\"ycFileInput\" type=\"file\" accept=\"image\/*\" capture=\"environment\">\n          <\/label>\n\n          <label>\n            <span id=\"ycCardTitleLabel\">Card title<\/span>\n            <input id=\"ycCardTitleInput\" type=\"text\" maxlength=\"36\" placeholder=\"My Story\">\n          <\/label>\n\n          <div class=\"yc-row-2\">\n            <label>\n              Output size\n              <select id=\"ycSizeSelect\"><\/select>\n            <\/label>\n\n            <label>\n              Fit mode\n              <select id=\"ycFitMode\">\n                <option value=\"cover\">Cover (center crop)<\/option>\n                <option value=\"contain\">Contain (show full image)<\/option>\n              <\/select>\n            <\/label>\n          <\/div>\n\n          <div class=\"yc-row-2\">\n            <label>\n              Card style\n              <select id=\"ycCardStyleSelect\">\n                <option value=\"simple\">Simple<\/option>\n                <option value=\"titlebar\">Title bar<\/option>\n                <option value=\"badge\">Badge<\/option>\n              <\/select>\n            <\/label>\n\n            <label>\n              Card background\n              <input id=\"ycCardBgColor\" type=\"color\" value=\"#ffffff\" style=\"height:46px;padding:6px;\">\n            <\/label>\n          <\/div>\n\n          <label>\n            Palette reduction: <span id=\"ycPaletteValue\">10<\/span> colors\n            <input id=\"ycPaletteRange\" type=\"range\" min=\"2\" max=\"32\" step=\"1\" value=\"10\">\n          <\/label>\n\n          <label>\n            Contrast: <span id=\"ycContrastValue\">14<\/span>\n            <input id=\"ycContrastRange\" type=\"range\" min=\"-50\" max=\"50\" step=\"1\" value=\"14\">\n          <\/label>\n\n          <label>\n            Saturation: <span id=\"ycSaturationValue\">12<\/span>\n            <input id=\"ycSaturationRange\" type=\"range\" min=\"-100\" max=\"100\" step=\"1\" value=\"12\">\n          <\/label>\n\n          <label>\n            Brightness: <span id=\"ycBrightnessValue\">2<\/span>\n            <input id=\"ycBrightnessRange\" type=\"range\" min=\"-50\" max=\"50\" step=\"1\" value=\"2\">\n          <\/label>\n\n          <label>\n            Source zoom: <span id=\"ycZoomValue\">115<\/span>%\n            <input id=\"ycZoomRange\" type=\"range\" min=\"50\" max=\"220\" step=\"1\" value=\"115\">\n          <\/label>\n\n          <label>\n            Horizontal offset: <span id=\"ycOffsetXValue\">0<\/span>\n            <input id=\"ycOffsetXRange\" type=\"range\" min=\"-100\" max=\"100\" step=\"1\" value=\"0\">\n          <\/label>\n\n          <label>\n            Vertical offset: <span id=\"ycOffsetYValue\">0<\/span>\n            <input id=\"ycOffsetYRange\" type=\"range\" min=\"-100\" max=\"100\" step=\"1\" value=\"0\">\n          <\/label>\n\n          <label>\n            Background color (contain mode)\n            <input id=\"ycBgColor\" type=\"color\" value=\"#000000\" style=\"height:46px;padding:6px;\">\n          <\/label>\n\n          <div class=\"yc-row-2\">\n            <button id=\"ycPresetBtn\" class=\"yc-btn-primary\" type=\"button\">Preset Yoto<\/button>\n            <button id=\"ycResetBtn\" type=\"button\">Reset<\/button>\n          <\/div>\n\n          <div class=\"yc-row-3\">\n            <button id=\"ycDownloadPixelBtn\" class=\"yc-btn-success\" type=\"button\">Download PNG<\/button>\n            <button id=\"ycDownloadCardBtn\" type=\"button\">Download Card PNG<\/button>\n            <button id=\"ycDownloadPdfBtn\" type=\"button\">Download Card PDF<\/button>\n          <\/div>\n\n          <div class=\"yc-help\">\n            Tip: on mobile, tapping the upload button can let you take a photo directly with your phone.\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"yc-panel\">\n        <h2>Preview<\/h2>\n\n        <div class=\"yc-preview-grid\">\n          <div class=\"yc-canvas-card\">\n            <div class=\"yc-canvas-title\">\n              <span>Pixel result<\/span>\n              <span id=\"ycOutputMeta\">16 \u00d7 16<\/span>\n            <\/div>\n            <canvas id=\"ycPreviewCanvas\" width=\"640\" height=\"640\"><\/canvas>\n          <\/div>\n\n          <div class=\"yc-canvas-card\">\n            <div class=\"yc-canvas-title\">\n              <span>Card preview<\/span>\n              <span id=\"ycCardMeta\">Simple<\/span>\n            <\/div>\n            <canvas id=\"ycCardPreviewCanvas\" width=\"900\" height=\"570\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"yc-stats\">\n          <div class=\"yc-stat\">\n            Output size\n            <strong id=\"ycStatSize\">16 \u00d7 16<\/strong>\n          <\/div>\n          <div class=\"yc-stat\">\n            Palette\n            <strong id=\"ycStatPalette\">10<\/strong>\n          <\/div>\n          <div class=\"yc-stat\">\n            Card style\n            <strong id=\"ycStatStyle\">Simple<\/strong>\n          <\/div>\n        <\/div>\n\n        <canvas id=\"ycSourceCanvas\" width=\"512\" height=\"512\" style=\"display:none;\"><\/canvas>\n        <canvas id=\"ycOutputCanvas\" width=\"16\" height=\"16\" style=\"display:none;\"><\/canvas>\n        <canvas id=\"ycWorkingCanvas\" width=\"256\" height=\"256\" style=\"display:none;\"><\/canvas>\n        <canvas id=\"ycCardCanvas\" width=\"900\" height=\"570\" style=\"display:none;\"><\/canvas>\n      <\/div>\n    <\/div>\n\n    <div class=\"yc-footer\">\n      Simple pixel image + printable card creator.\n    <\/div>\n  <\/div>\n\n  <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/jspdf@2.5.1\/dist\/jspdf.umd.min.js\"><\/script>\n\n  <script>\n    (function(){\n      const APP_CONFIG = {\n        title: \"Create a Yoto Card\",\n        subtitle: \"Upload an image or take a photo, turn it into pixel art, add a title, and export both the icon and the printable card.\",\n        sizeOptions: [16, 24, 32, 48, 64],\n        defaults: {\n          size: 16,\n          fitMode: \"cover\",\n          cardStyle: \"simple\",\n          palette: 10,\n          contrast: 14,\n          saturation: 12,\n          brightness: 2,\n          zoom: 115,\n          offsetX: 0,\n          offsetY: 0,\n          bgColor: \"#000000\",\n          cardBgColor: \"#ffffff\",\n          cardTitle: \"My Story\"\n        }\n      };\n\n      const app = document.getElementById('yoto-card-creator-app');\n\n      const sourceCanvas = document.getElementById('ycSourceCanvas');\n      const outputCanvas = document.getElementById('ycOutputCanvas');\n      const workingCanvas = document.getElementById('ycWorkingCanvas');\n      const previewCanvas = document.getElementById('ycPreviewCanvas');\n      const cardCanvas = document.getElementById('ycCardCanvas');\n      const cardPreviewCanvas = document.getElementById('ycCardPreviewCanvas');\n\n      const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });\n      const outputCtx = outputCanvas.getContext('2d', { willReadFrequently: true });\n      const workingCtx = workingCanvas.getContext('2d', { willReadFrequently: true });\n      const previewCtx = previewCanvas.getContext('2d');\n      const cardCtx = cardCanvas.getContext('2d');\n      const cardPreviewCtx = cardPreviewCanvas.getContext('2d');\n\n      const fileInput = document.getElementById('ycFileInput');\n      const cardTitleInput = document.getElementById('ycCardTitleInput');\n      const sizeSelect = document.getElementById('ycSizeSelect');\n      const fitMode = document.getElementById('ycFitMode');\n      const cardStyleSelect = document.getElementById('ycCardStyleSelect');\n      const cardBgColorInput = document.getElementById('ycCardBgColor');\n      const paletteRange = document.getElementById('ycPaletteRange');\n      const contrastRange = document.getElementById('ycContrastRange');\n      const saturationRange = document.getElementById('ycSaturationRange');\n      const brightnessRange = document.getElementById('ycBrightnessRange');\n      const zoomRange = document.getElementById('ycZoomRange');\n      const offsetXRange = document.getElementById('ycOffsetXRange');\n      const offsetYRange = document.getElementById('ycOffsetYRange');\n      const bgColorInput = document.getElementById('ycBgColor');\n\n      const paletteValue = document.getElementById('ycPaletteValue');\n      const contrastValue = document.getElementById('ycContrastValue');\n      const saturationValue = document.getElementById('ycSaturationValue');\n      const brightnessValue = document.getElementById('ycBrightnessValue');\n      const zoomValue = document.getElementById('ycZoomValue');\n      const offsetXValue = document.getElementById('ycOffsetXValue');\n      const offsetYValue = document.getElementById('ycOffsetYValue');\n\n      const outputMeta = document.getElementById('ycOutputMeta');\n      const cardMeta = document.getElementById('ycCardMeta');\n      const statSize = document.getElementById('ycStatSize');\n      const statPalette = document.getElementById('ycStatPalette');\n      const statStyle = document.getElementById('ycStatStyle');\n\n      const presetBtn = document.getElementById('ycPresetBtn');\n      const resetBtn = document.getElementById('ycResetBtn');\n      const downloadPixelBtn = document.getElementById('ycDownloadPixelBtn');\n      const downloadCardBtn = document.getElementById('ycDownloadCardBtn');\n      const downloadPdfBtn = document.getElementById('ycDownloadPdfBtn');\n      const dropZone = document.getElementById('ycDropZone');\n\n      let img = null;\n      let currentFileName = 'yoto-card';\n\n      document.getElementById('ycTitle').textContent = APP_CONFIG.title;\n      document.getElementById('ycSubtitle').textContent = APP_CONFIG.subtitle;\n\n      APP_CONFIG.sizeOptions.forEach(size => {\n        const option = document.createElement('option');\n        option.value = size;\n        option.textContent = `${size} \u00d7 ${size}`;\n        if (size === APP_CONFIG.defaults.size) option.selected = true;\n        sizeSelect.appendChild(option);\n      });\n\n      function titleCaseStyleName(value){\n        if(value === 'titlebar') return 'Title bar';\n        if(value === 'badge') return 'Badge';\n        return 'Simple';\n      }\n\n      function updateLabels(){\n        paletteValue.textContent = paletteRange.value;\n        contrastValue.textContent = contrastRange.value;\n        saturationValue.textContent = saturationRange.value;\n        brightnessValue.textContent = brightnessRange.value;\n        zoomValue.textContent = zoomRange.value;\n        offsetXValue.textContent = offsetXRange.value;\n        offsetYValue.textContent = offsetYRange.value;\n        outputMeta.textContent = `${sizeSelect.value} \u00d7 ${sizeSelect.value}`;\n        statSize.textContent = `${sizeSelect.value} \u00d7 ${sizeSelect.value}`;\n        statPalette.textContent = paletteRange.value;\n        cardMeta.textContent = titleCaseStyleName(cardStyleSelect.value);\n        statStyle.textContent = titleCaseStyleName(cardStyleSelect.value);\n      }\n\n      function hexToRgb(hex){\n        const clean = hex.replace('#','');\n        return {\n          r: parseInt(clean.substring(0,2),16),\n          g: parseInt(clean.substring(2,4),16),\n          b: parseInt(clean.substring(4,6),16)\n        };\n      }\n\n      function roundedRect(ctx, x, y, width, height, radius){\n        ctx.beginPath();\n        ctx.moveTo(x + radius, y);\n        ctx.lineTo(x + width - radius, y);\n        ctx.quadraticCurveTo(x + width, y, x + width, y + radius);\n        ctx.lineTo(x + width, y + height - radius);\n        ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);\n        ctx.lineTo(x + radius, y + height);\n        ctx.quadraticCurveTo(x, y + height, x, y + height - radius);\n        ctx.lineTo(x, y + radius);\n        ctx.quadraticCurveTo(x, y, x + radius, y);\n        ctx.closePath();\n      }\n\n      function fitTitleText(ctx, text, maxWidth, maxSize, minSize){\n        let size = maxSize;\n        while(size >= minSize){\n          ctx.font = `700 ${size}px Inter, Arial, sans-serif`;\n          if(ctx.measureText(text).width <= maxWidth) return size;\n          size--;\n        }\n        return minSize;\n      }\n\n      function applyAdjustments(imageData){\n        const data = imageData.data;\n        const contrast = Number(contrastRange.value);\n        const saturation = Number(saturationRange.value) \/ 100;\n        const brightness = Number(brightnessRange.value);\n\n        const factor = (259 * (contrast + 255)) \/ (255 * (259 - contrast));\n\n        for(let i=0;i<data.length;i+=4){\n          let r = data[i];\n          let g = data[i+1];\n          let b = data[i+2];\n\n          r += brightness;\n          g += brightness;\n          b += brightness;\n\n          r = factor * (r - 128) + 128;\n          g = factor * (g - 128) + 128;\n          b = factor * (b - 128) + 128;\n\n          const gray = 0.299*r + 0.587*g + 0.114*b;\n          r = gray + (r - gray) * (1 + saturation);\n          g = gray + (g - gray) * (1 + saturation);\n          b = gray + (b - gray) * (1 + saturation);\n\n          data[i]   = Math.max(0, Math.min(255, Math.round(r)));\n          data[i+1] = Math.max(0, Math.min(255, Math.round(g)));\n          data[i+2] = Math.max(0, Math.min(255, Math.round(b)));\n        }\n\n        return imageData;\n      }\n\n      function quantize(imageData, colorCount){\n        const data = imageData.data;\n        const levels = Math.max(2, Math.round(Math.cbrt(colorCount)));\n        const step = levels <= 1 ? 255 : 255 \/ (levels - 1);\n\n        for(let i=0;i<data.length;i+=4){\n          data[i]   = Math.round(data[i] \/ step) * step;\n          data[i+1] = Math.round(data[i+1] \/ step) * step;\n          data[i+2] = Math.round(data[i+2] \/ step) * step;\n        }\n        return imageData;\n      }\n\n      function drawSourceCrop(){\n        const W = sourceCanvas.width;\n        const H = sourceCanvas.height;\n\n        sourceCtx.clearRect(0,0,W,H);\n        sourceCtx.fillStyle = '#0a0d18';\n        sourceCtx.fillRect(0,0,W,H);\n\n        if(!img) return;\n\n        const mode = fitMode.value;\n        const zoom = Number(zoomRange.value) \/ 100;\n        const ox = Number(offsetXRange.value) \/ 100;\n        const oy = Number(offsetYRange.value) \/ 100;\n\n        let drawW, drawH;\n        const imgRatio = img.width \/ img.height;\n        const boxRatio = W \/ H;\n\n        if(mode === 'cover'){\n          if(imgRatio > boxRatio){\n            drawH = H * zoom;\n            drawW = drawH * imgRatio;\n          }else{\n            drawW = W * zoom;\n            drawH = drawW \/ imgRatio;\n          }\n        }else{\n          if(imgRatio > boxRatio){\n            drawW = W * zoom;\n            drawH = drawW \/ imgRatio;\n          }else{\n            drawH = H * zoom;\n            drawW = drawH * imgRatio;\n          }\n          const bg = hexToRgb(bgColorInput.value);\n          sourceCtx.fillStyle = `rgb(${bg.r},${bg.g},${bg.b})`;\n          sourceCtx.fillRect(0,0,W,H);\n        }\n\n        const x = (W - drawW)\/2 + ox * (W * 0.45);\n        const y = (H - drawH)\/2 + oy * (H * 0.45);\n\n        sourceCtx.imageSmoothingEnabled = true;\n        sourceCtx.drawImage(img, x, y, drawW, drawH);\n      }\n\n      function buildPixelArt(){\n        const size = Number(sizeSelect.value);\n        outputCanvas.width = size;\n        outputCanvas.height = size;\n\n        workingCanvas.width = size;\n        workingCanvas.height = size;\n        workingCtx.clearRect(0,0,size,size);\n\n        if(!img){\n          outputCtx.clearRect(0,0,size,size);\n          return;\n        }\n\n        workingCtx.imageSmoothingEnabled = true;\n        workingCtx.drawImage(sourceCanvas, 0, 0, size, size);\n\n        let imageData = workingCtx.getImageData(0,0,size,size);\n        imageData = applyAdjustments(imageData);\n        imageData = quantize(imageData, Number(paletteRange.value));\n        workingCtx.putImageData(imageData, 0, 0);\n\n        outputCtx.imageSmoothingEnabled = false;\n        outputCtx.clearRect(0,0,size,size);\n        outputCtx.drawImage(workingCanvas, 0, 0, size, size);\n      }\n\n      function drawPixelPreview(){\n        const size = Number(sizeSelect.value);\n        const W = previewCanvas.width;\n        const H = previewCanvas.height;\n\n        previewCtx.clearRect(0,0,W,H);\n        previewCtx.fillStyle = '#0a0d18';\n        previewCtx.fillRect(0,0,W,H);\n\n        const pad = 12;\n        const scale = Math.floor((W - pad * 2) \/ size);\n        const drawSize = scale * size;\n        const x = Math.floor((W - drawSize) \/ 2);\n        const y = Math.floor((H - drawSize) \/ 2);\n\n        previewCtx.imageSmoothingEnabled = false;\n        previewCtx.drawImage(outputCanvas, x, y, drawSize, drawSize);\n\n        previewCtx.strokeStyle = 'rgba(255,255,255,.12)';\n        previewCtx.lineWidth = 1;\n\n        for(let i=0;i<=size;i++){\n          const p = x + i * scale;\n          previewCtx.beginPath();\n          previewCtx.moveTo(p, y);\n          previewCtx.lineTo(p, y + drawSize);\n          previewCtx.stroke();\n        }\n\n        for(let i=0;i<=size;i++){\n          const p = y + i * scale;\n          previewCtx.beginPath();\n          previewCtx.moveTo(x, p);\n          previewCtx.lineTo(x + drawSize, p);\n          previewCtx.stroke();\n        }\n      }\n\n      function drawCard(){\n        const W = cardCanvas.width;\n        const H = cardCanvas.height;\n        const cardBg = cardBgColorInput.value;\n        const style = cardStyleSelect.value;\n        const title = (cardTitleInput.value || APP_CONFIG.defaults.cardTitle).trim();\n\n        cardCtx.clearRect(0,0,W,H);\n\n        roundedRect(cardCtx, 20, 20, W - 40, H - 40, 28);\n        cardCtx.fillStyle = cardBg;\n        cardCtx.fill();\n        cardCtx.lineWidth = 4;\n        cardCtx.strokeStyle = '#d6dbe8';\n        cardCtx.stroke();\n\n        const titleMaxWidth = W - 140;\n        let titleSize;\n\n        if(style === 'simple'){\n          const iconSize = 240;\n          const iconX = (W - iconSize) \/ 2;\n          const iconY = 90;\n\n          cardCtx.fillStyle = '#f3f5fa';\n          roundedRect(cardCtx, iconX - 18, iconY - 18, iconSize + 36, iconSize + 36, 24);\n          cardCtx.fill();\n\n          cardCtx.imageSmoothingEnabled = false;\n          cardCtx.drawImage(outputCanvas, iconX, iconY, iconSize, iconSize);\n\n          titleSize = fitTitleText(cardCtx, title, titleMaxWidth, 56, 28);\n          cardCtx.font = `700 ${titleSize}px Inter, Arial, sans-serif`;\n          cardCtx.fillStyle = '#1b2238';\n          cardCtx.textAlign = 'center';\n          cardCtx.textBaseline = 'middle';\n          cardCtx.fillText(title, W \/ 2, H - 110);\n        }\n\n        if(style === 'titlebar'){\n          cardCtx.fillStyle = '#1e294a';\n          roundedRect(cardCtx, 45, 45, W - 90, 90, 18);\n          cardCtx.fill();\n\n          titleSize = fitTitleText(cardCtx, title, W - 150, 46, 24);\n          cardCtx.font = `700 ${titleSize}px Inter, Arial, sans-serif`;\n          cardCtx.fillStyle = '#ffffff';\n          cardCtx.textAlign = 'center';\n          cardCtx.textBaseline = 'middle';\n          cardCtx.fillText(title, W \/ 2, 90);\n\n          const iconSize = 260;\n          const iconX = (W - iconSize) \/ 2;\n          const iconY = 180;\n\n          cardCtx.fillStyle = '#f4f6fb';\n          roundedRect(cardCtx, iconX - 16, iconY - 16, iconSize + 32, iconSize + 32, 22);\n          cardCtx.fill();\n\n          cardCtx.imageSmoothingEnabled = false;\n          cardCtx.drawImage(outputCanvas, iconX, iconY, iconSize, iconSize);\n        }\n\n        if(style === 'badge'){\n          const iconSize = 240;\n          const iconX = (W - iconSize) \/ 2;\n          const iconY = 95;\n\n          cardCtx.fillStyle = '#f4f6fb';\n          roundedRect(cardCtx, iconX - 18, iconY - 18, iconSize + 36, iconSize + 36, 24);\n          cardCtx.fill();\n\n          cardCtx.imageSmoothingEnabled = false;\n          cardCtx.drawImage(outputCanvas, iconX, iconY, iconSize, iconSize);\n\n          const badgeW = Math.min(520, Math.max(260, title.length * 18));\n          const badgeH = 74;\n          const badgeX = (W - badgeW) \/ 2;\n          const badgeY = H - 130;\n\n          cardCtx.fillStyle = '#f0b224';\n          roundedRect(cardCtx, badgeX, badgeY, badgeW, badgeH, 37);\n          cardCtx.fill();\n\n          titleSize = fitTitleText(cardCtx, title, badgeW - 60, 38, 22);\n          cardCtx.font = `700 ${titleSize}px Inter, Arial, sans-serif`;\n          cardCtx.fillStyle = '#1b180d';\n          cardCtx.textAlign = 'center';\n          cardCtx.textBaseline = 'middle';\n          cardCtx.fillText(title, W \/ 2, badgeY + badgeH \/ 2);\n        }\n      }\n\n      function drawCardPreview(){\n        const W = cardPreviewCanvas.width;\n        const H = cardPreviewCanvas.height;\n\n        cardPreviewCtx.clearRect(0,0,W,H);\n        cardPreviewCtx.fillStyle = '#0a0d18';\n        cardPreviewCtx.fillRect(0,0,W,H);\n\n        const pad = 22;\n        cardPreviewCtx.drawImage(cardCanvas, pad, pad, W - pad * 2, H - pad * 2);\n      }\n\n      function renderAll(){\n        updateLabels();\n        drawSourceCrop();\n        buildPixelArt();\n        drawPixelPreview();\n        drawCard();\n        drawCardPreview();\n      }\n\n      function handleFile(file){\n        if(!file || !file.type.startsWith('image\/')) return;\n\n        currentFileName = (file.name || 'yoto-card').replace(\/\\.[^.]+$\/, '');\n        const reader = new FileReader();\n\n        reader.onload = (e) => {\n          const image = new Image();\n          image.onload = () => {\n            img = image;\n            renderAll();\n          };\n          image.src = e.target.result;\n        };\n\n        reader.readAsDataURL(file);\n      }\n\n      function downloadCanvas(canvas, name){\n        const a = document.createElement('a');\n        a.href = canvas.toDataURL('image\/png');\n        a.download = name;\n        a.click();\n      }\n\n      async function downloadPdf(){\n        const { jsPDF } = window.jspdf;\n        const pdf = new jsPDF({\n          orientation: 'landscape',\n          unit: 'mm',\n          format: 'a4'\n        });\n\n        const pageW = pdf.internal.pageSize.getWidth();\n        const pageH = pdf.internal.pageSize.getHeight();\n\n        const cardW = 86;\n        const cardH = 54;\n\n        const x = (pageW - cardW) \/ 2;\n        const y = (pageH - cardH) \/ 2;\n\n        const dataUrl = cardCanvas.toDataURL('image\/png');\n\n        pdf.addImage(dataUrl, 'PNG', x, y, cardW, cardH);\n        pdf.save(`${currentFileName}-card.pdf`);\n      }\n\n      function applyDefaults(){\n        sizeSelect.value = APP_CONFIG.defaults.size;\n        fitMode.value = APP_CONFIG.defaults.fitMode;\n        cardStyleSelect.value = APP_CONFIG.defaults.cardStyle;\n        paletteRange.value = APP_CONFIG.defaults.palette;\n        contrastRange.value = APP_CONFIG.defaults.contrast;\n        saturationRange.value = APP_CONFIG.defaults.saturation;\n        brightnessRange.value = APP_CONFIG.defaults.brightness;\n        zoomRange.value = APP_CONFIG.defaults.zoom;\n        offsetXRange.value = APP_CONFIG.defaults.offsetX;\n        offsetYRange.value = APP_CONFIG.defaults.offsetY;\n        bgColorInput.value = APP_CONFIG.defaults.bgColor;\n        cardBgColorInput.value = APP_CONFIG.defaults.cardBgColor;\n        cardTitleInput.value = APP_CONFIG.defaults.cardTitle;\n      }\n\n      app.querySelectorAll('input, select').forEach(el => {\n        el.addEventListener('input', renderAll);\n        el.addEventListener('change', renderAll);\n      });\n\n      presetBtn.addEventListener('click', () => {\n        applyDefaults();\n        renderAll();\n      });\n\n      resetBtn.addEventListener('click', () => {\n        applyDefaults();\n        renderAll();\n      });\n\n      fileInput.addEventListener('change', e => handleFile(e.target.files[0]));\n\n      downloadPixelBtn.addEventListener('click', () => {\n        const size = Number(sizeSelect.value);\n        downloadCanvas(outputCanvas, `${currentFileName}-${size}x${size}.png`);\n      });\n\n      downloadCardBtn.addEventListener('click', () => {\n        downloadCanvas(cardCanvas, `${currentFileName}-card.png`);\n      });\n\n      downloadPdfBtn.addEventListener('click', downloadPdf);\n\n      ['dragenter','dragover'].forEach(ev => {\n        dropZone.addEventListener(ev, e => {\n          e.preventDefault();\n          dropZone.classList.add('drag');\n        });\n      });\n\n      ['dragleave','drop'].forEach(ev => {\n        dropZone.addEventListener(ev, e => {\n          e.preventDefault();\n          dropZone.classList.remove('drag');\n        });\n      });\n\n      dropZone.addEventListener('drop', e => {\n        const file = e.dataTransfer.files[0];\n        handleFile(file);\n      });\n\n      applyDefaults();\n      updateLabels();\n      renderAll();\n    })();\n  <\/script>\n<\/div>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-fe72622 e-flex e-con-boxed e-con e-parent\" data-id=\"fe72622\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b3c9578 elementor-widget elementor-widget-html\" data-id=\"b3c9578\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<div id=\"yoto-editor-app\">\n  <style>\n    #yoto-editor-app{\n      --bg1:#0c1020;\n      --bg2:#131935;\n      --panel:#171b2e;\n      --panel-2:#1f2440;\n      --text:#f3f5ff;\n      --muted:#aeb6d9;\n      --accent:#f0b224;\n      --accent-2:#ffd36a;\n      --success:#68d391;\n      --border:#2f365d;\n      --radius:18px;\n      --shadow:0 12px 40px rgba(0,0,0,.22);\n      font-family:Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;\n      color:var(--text);\n    }\n\n    #yoto-editor-app *{\n      box-sizing:border-box;\n    }\n\n    #yoto-editor-app .ye-wrap{\n      max-width:1200px;\n      margin:0 auto;\n      padding:20px;\n      background:linear-gradient(180deg,var(--bg1) 0%, var(--bg2) 100%);\n      border-radius:24px;\n    }\n\n    #yoto-editor-app .ye-header{\n      margin-bottom:20px;\n    }\n\n    #yoto-editor-app .ye-title{\n      margin:0 0 8px;\n      font-size:clamp(28px,4vw,40px);\n      line-height:1.05;\n    }\n\n    #yoto-editor-app .ye-subtitle{\n      margin:0;\n      color:var(--muted);\n      font-size:16px;\n      line-height:1.5;\n    }\n\n    #yoto-editor-app .ye-grid{\n      display:grid;\n      grid-template-columns:380px 1fr;\n      gap:18px;\n    }\n\n    #yoto-editor-app .ye-panel{\n      background:rgba(23,27,46,.96);\n      border:1px solid var(--border);\n      border-radius:var(--radius);\n      padding:16px;\n      box-shadow:var(--shadow);\n    }\n\n    #yoto-editor-app .ye-panel h2{\n      margin:0 0 12px;\n      font-size:18px;\n    }\n\n    #yoto-editor-app .ye-controls{\n      display:grid;\n      gap:12px;\n    }\n\n    #yoto-editor-app label{\n      display:grid;\n      gap:6px;\n      color:var(--muted);\n      font-size:14px;\n    }\n\n    #yoto-editor-app input[type=\"file\"],\n    #yoto-editor-app input[type=\"range\"],\n    #yoto-editor-app input[type=\"color\"],\n    #yoto-editor-app select,\n    #yoto-editor-app button{\n      width:100%;\n    }\n\n    #yoto-editor-app input[type=\"file\"],\n    #yoto-editor-app select,\n    #yoto-editor-app button{\n      border-radius:12px;\n      border:1px solid var(--border);\n      background:var(--panel-2);\n      color:var(--text);\n      padding:12px;\n      font-size:14px;\n    }\n\n    #yoto-editor-app button{\n      cursor:pointer;\n      font-weight:600;\n      transition:transform .08s ease, border-color .2s ease;\n    }\n\n    #yoto-editor-app button:hover{\n      border-color:#5a68a9;\n    }\n\n    #yoto-editor-app button:active{\n      transform:translateY(1px);\n    }\n\n    #yoto-editor-app .ye-btn-primary{\n      background:linear-gradient(180deg,var(--accent-2),var(--accent));\n      color:#261600;\n      border:none;\n    }\n\n    #yoto-editor-app .ye-btn-success{\n      background:linear-gradient(180deg,#7be4a4,#4dc979);\n      color:#082313;\n      border:none;\n    }\n\n    #yoto-editor-app .ye-row-2{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:10px;\n    }\n\n    #yoto-editor-app .ye-dropzone{\n      border:1.5px dashed #56639d;\n      border-radius:14px;\n      padding:16px;\n      text-align:center;\n      color:var(--muted);\n      background:#10152a;\n    }\n\n    #yoto-editor-app .ye-dropzone.drag{\n      border-color:var(--accent);\n      background:#171e37;\n      color:#fff;\n    }\n\n    #yoto-editor-app .ye-help{\n      font-size:12px;\n      color:var(--muted);\n      line-height:1.5;\n    }\n\n    #yoto-editor-app .ye-preview-grid{\n      display:grid;\n      grid-template-columns:1fr 1fr;\n      gap:18px;\n    }\n\n    #yoto-editor-app .ye-canvas-card{\n      background:#101426;\n      border:1px solid var(--border);\n      border-radius:16px;\n      padding:12px;\n    }\n\n    #yoto-editor-app .ye-canvas-title{\n      display:flex;\n      align-items:center;\n      justify-content:space-between;\n      gap:12px;\n      margin-bottom:10px;\n      color:var(--muted);\n      font-size:14px;\n    }\n\n    #yoto-editor-app canvas{\n      width:100%;\n      display:block;\n      border-radius:12px;\n      background:#0a0d18;\n      image-rendering:pixelated;\n      image-rendering:crisp-edges;\n    }\n\n    #yoto-editor-app .ye-stats{\n      display:grid;\n      grid-template-columns:repeat(3,1fr);\n      gap:10px;\n      margin-top:14px;\n    }\n\n    #yoto-editor-app .ye-stat{\n      background:#11162b;\n      border:1px solid var(--border);\n      border-radius:12px;\n      padding:10px;\n      color:var(--muted);\n      font-size:13px;\n    }\n\n    #yoto-editor-app .ye-stat strong{\n      display:block;\n      margin-top:4px;\n      font-size:18px;\n      color:#fff;\n    }\n\n    #yoto-editor-app .ye-footer{\n      margin-top:18px;\n      color:var(--muted);\n      font-size:13px;\n      text-align:center;\n    }\n\n    @media (max-width:980px){\n      #yoto-editor-app .ye-grid{\n        grid-template-columns:1fr;\n      }\n      #yoto-editor-app .ye-preview-grid{\n        grid-template-columns:1fr;\n      }\n    }\n  <\/style>\n\n  <div class=\"ye-wrap\">\n    <div class=\"ye-header\">\n      <h1 id=\"yeAppTitle\" class=\"ye-title\">Create a Yoto Image<\/h1>\n      <p id=\"yeAppSubtitle\" class=\"ye-subtitle\">\n        Upload an image, pixelize it, preview the result, and download a clean Yoto-style version.\n      <\/p>\n    <\/div>\n\n    <div class=\"ye-grid\">\n      <div class=\"ye-panel\">\n        <h2 id=\"yeSettingsTitle\">Settings<\/h2>\n\n        <div class=\"ye-controls\">\n          <div id=\"yeDropZone\" class=\"ye-dropzone\">\n            <span id=\"yeDropMain\">Drop your image here<\/span><br>\n            <span id=\"yeDropSub\" class=\"ye-help\">or use the upload button below<\/span>\n          <\/div>\n\n          <label>\n            <span id=\"yeUploadLabel\">Upload image<\/span>\n            <input id=\"yeFileInput\" type=\"file\" accept=\"image\/*\">\n          <\/label>\n\n          <div class=\"ye-row-2\">\n            <label>\n              <span id=\"yeOutputSizeLabel\">Output size<\/span>\n              <select id=\"yeSizeSelect\"><\/select>\n            <\/label>\n\n            <label>\n              <span id=\"yeFitModeLabel\">Fit mode<\/span>\n              <select id=\"yeFitMode\">\n                <option value=\"cover\">Cover (center crop)<\/option>\n                <option value=\"contain\">Contain (show full image)<\/option>\n              <\/select>\n            <\/label>\n          <\/div>\n\n          <label>\n            <span id=\"yePaletteLabel\">Palette reduction: <span id=\"yePaletteValue\">10<\/span> colors<\/span>\n            <input id=\"yePaletteRange\" type=\"range\" min=\"2\" max=\"32\" step=\"1\" value=\"10\">\n          <\/label>\n\n          <label>\n            <span id=\"yeContrastLabel\">Contrast: <span id=\"yeContrastValue\">14<\/span><\/span>\n            <input id=\"yeContrastRange\" type=\"range\" min=\"-50\" max=\"50\" step=\"1\" value=\"14\">\n          <\/label>\n\n          <label>\n            <span id=\"yeSaturationLabel\">Saturation: <span id=\"yeSaturationValue\">12<\/span><\/span>\n            <input id=\"yeSaturationRange\" type=\"range\" min=\"-100\" max=\"100\" step=\"1\" value=\"12\">\n          <\/label>\n\n          <label>\n            <span id=\"yeBrightnessLabel\">Brightness: <span id=\"yeBrightnessValue\">2<\/span><\/span>\n            <input id=\"yeBrightnessRange\" type=\"range\" min=\"-50\" max=\"50\" step=\"1\" value=\"2\">\n          <\/label>\n\n          <label>\n            <span id=\"yeZoomLabel\">Source zoom: <span id=\"yeZoomValue\">115<\/span>%<\/span>\n            <input id=\"yeZoomRange\" type=\"range\" min=\"50\" max=\"220\" step=\"1\" value=\"115\">\n          <\/label>\n\n          <label>\n            <span id=\"yeOffsetXLabel\">Horizontal offset: <span id=\"yeOffsetXValue\">0<\/span><\/span>\n            <input id=\"yeOffsetXRange\" type=\"range\" min=\"-100\" max=\"100\" step=\"1\" value=\"0\">\n          <\/label>\n\n          <label>\n            <span id=\"yeOffsetYLabel\">Vertical offset: <span id=\"yeOffsetYValue\">0<\/span><\/span>\n            <input id=\"yeOffsetYRange\" type=\"range\" min=\"-100\" max=\"100\" step=\"1\" value=\"0\">\n          <\/label>\n\n          <label>\n            <span id=\"yeBgColorLabel\">Background color (contain mode)<\/span>\n            <input id=\"yeBgColor\" type=\"color\" value=\"#000000\" style=\"height:46px;padding:6px;\">\n          <\/label>\n\n          <div class=\"ye-row-2\">\n            <button id=\"yePresetBtn\" class=\"ye-btn-primary\" type=\"button\">Preset Yoto<\/button>\n            <button id=\"yeResetBtn\" type=\"button\">Reset<\/button>\n          <\/div>\n\n          <div class=\"ye-row-2\">\n            <button id=\"yeDownloadBtn\" class=\"ye-btn-success\" type=\"button\">Download PNG<\/button>\n            <button id=\"yeCopyBtn\" type=\"button\">Copy image<\/button>\n          <\/div>\n\n          <button id=\"yeDownloadPreviewBtn\" type=\"button\">Download enlarged preview<\/button>\n\n          <div id=\"yeTipText\" class=\"ye-help\">\n            Tip: for a true Yoto-style result, start with <strong>16\u00d716<\/strong>, keep the palette short,\n            and slightly increase contrast.\n          <\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"ye-panel\">\n        <h2 id=\"yePreviewTitle\">Preview<\/h2>\n\n        <div class=\"ye-preview-grid\">\n          <div class=\"ye-canvas-card\">\n            <div class=\"ye-canvas-title\">\n              <span id=\"yeSourceTitle\">Source crop<\/span>\n              <span id=\"yeSourceMeta\">No image loaded<\/span>\n            <\/div>\n            <canvas id=\"yeSourceCanvas\" width=\"512\" height=\"512\"><\/canvas>\n          <\/div>\n\n          <div class=\"ye-canvas-card\">\n            <div class=\"ye-canvas-title\">\n              <span id=\"yeResultTitle\">Pixel result<\/span>\n              <span id=\"yeOutputMeta\">16 \u00d7 16<\/span>\n            <\/div>\n            <canvas id=\"yePreviewCanvas\" width=\"640\" height=\"640\"><\/canvas>\n          <\/div>\n        <\/div>\n\n        <div class=\"ye-stats\">\n          <div class=\"ye-stat\">\n            <span id=\"yeStatSizeLabel\">Output size<\/span>\n            <strong id=\"yeStatSize\">16 \u00d7 16<\/strong>\n          <\/div>\n          <div class=\"ye-stat\">\n            <span id=\"yeStatPaletteLabel\">Palette<\/span>\n            <strong id=\"yeStatPalette\">10<\/strong>\n          <\/div>\n          <div class=\"ye-stat\">\n            <span id=\"yeStatFormatLabel\">Format<\/span>\n            <strong id=\"yeStatFormat\">PNG<\/strong>\n          <\/div>\n        <\/div>\n\n        <canvas id=\"yeOutputCanvas\" width=\"16\" height=\"16\" style=\"display:none;\"><\/canvas>\n        <canvas id=\"yeWorkingCanvas\" width=\"256\" height=\"256\" style=\"display:none;\"><\/canvas>\n      <\/div>\n    <\/div>\n\n    <div id=\"yeFooterText\" class=\"ye-footer\">\n      Built for simple Yoto-style image creation.\n    <\/div>\n  <\/div>\n\n  <script>\n    (function(){\n      \/* =========================================\n         EASY CUSTOM SETTINGS\n      ========================================= *\/\n\n      const APP_CONFIG = {\n        title: \"Create a Yoto Image\",\n        subtitle: \"Upload an image, pixelize it, preview the result, and download a clean Yoto-style version.\",\n        settingsTitle: \"Settings\",\n        previewTitle: \"Preview\",\n        sourceTitle: \"Source crop\",\n        resultTitle: \"Pixel result\",\n        dropMain: \"Drop your image here\",\n        dropSub: \"or use the upload button below\",\n        uploadLabel: \"Upload image\",\n        outputSizeLabel: \"Output size\",\n        fitModeLabel: \"Fit mode\",\n        bgColorLabel: \"Background color (contain mode)\",\n        presetBtn: \"Preset Yoto\",\n        resetBtn: \"Reset\",\n        downloadBtn: \"Download PNG\",\n        copyBtn: \"Copy image\",\n        downloadPreviewBtn: \"Download enlarged preview\",\n        footerText: \"Built for simple Yoto-style image creation.\",\n        tipText: 'Tip: for a true Yoto-style result, start with <strong>16\u00d716<\/strong>, keep the palette short, and slightly increase contrast.',\n        sizeOptions: [16, 24, 32, 48, 64],\n        defaults: {\n          size: 16,\n          fitMode: \"cover\",\n          palette: 10,\n          contrast: 14,\n          saturation: 12,\n          brightness: 2,\n          zoom: 115,\n          offsetX: 0,\n          offsetY: 0,\n          bgColor: \"#000000\"\n        }\n      };\n\n      \/* =========================================\n         ELEMENTS\n      ========================================= *\/\n\n      const app = document.getElementById('yoto-editor-app');\n\n      const sourceCanvas = document.getElementById('yeSourceCanvas');\n      const previewCanvas = document.getElementById('yePreviewCanvas');\n      const outputCanvas = document.getElementById('yeOutputCanvas');\n      const workingCanvas = document.getElementById('yeWorkingCanvas');\n\n      const sourceCtx = sourceCanvas.getContext('2d', { willReadFrequently: true });\n      const previewCtx = previewCanvas.getContext('2d');\n      const outputCtx = outputCanvas.getContext('2d', { willReadFrequently: true });\n      const workingCtx = workingCanvas.getContext('2d', { willReadFrequently: true });\n\n      const fileInput = document.getElementById('yeFileInput');\n      const sizeSelect = document.getElementById('yeSizeSelect');\n      const fitMode = document.getElementById('yeFitMode');\n      const paletteRange = document.getElementById('yePaletteRange');\n      const contrastRange = document.getElementById('yeContrastRange');\n      const saturationRange = document.getElementById('yeSaturationRange');\n      const brightnessRange = document.getElementById('yeBrightnessRange');\n      const zoomRange = document.getElementById('yeZoomRange');\n      const offsetXRange = document.getElementById('yeOffsetXRange');\n      const offsetYRange = document.getElementById('yeOffsetYRange');\n      const bgColorInput = document.getElementById('yeBgColor');\n\n      const paletteValue = document.getElementById('yePaletteValue');\n      const contrastValue = document.getElementById('yeContrastValue');\n      const saturationValue = document.getElementById('yeSaturationValue');\n      const brightnessValue = document.getElementById('yeBrightnessValue');\n      const zoomValue = document.getElementById('yeZoomValue');\n      const offsetXValue = document.getElementById('yeOffsetXValue');\n      const offsetYValue = document.getElementById('yeOffsetYValue');\n\n      const sourceMeta = document.getElementById('yeSourceMeta');\n      const outputMeta = document.getElementById('yeOutputMeta');\n      const statSize = document.getElementById('yeStatSize');\n      const statPalette = document.getElementById('yeStatPalette');\n\n      const presetBtn = document.getElementById('yePresetBtn');\n      const resetBtn = document.getElementById('yeResetBtn');\n      const downloadBtn = document.getElementById('yeDownloadBtn');\n      const copyBtn = document.getElementById('yeCopyBtn');\n      const downloadPreviewBtn = document.getElementById('yeDownloadPreviewBtn');\n      const dropZone = document.getElementById('yeDropZone');\n\n      let img = null;\n      let currentFileName = 'yoto-image';\n\n      \/* =========================================\n         INIT UI TEXTS\n      ========================================= *\/\n\n      function setText(id, value){\n        const el = document.getElementById(id);\n        if(el) el.innerHTML = value;\n      }\n\n      setText('yeAppTitle', APP_CONFIG.title);\n      setText('yeAppSubtitle', APP_CONFIG.subtitle);\n      setText('yeSettingsTitle', APP_CONFIG.settingsTitle);\n      setText('yePreviewTitle', APP_CONFIG.previewTitle);\n      setText('yeSourceTitle', APP_CONFIG.sourceTitle);\n      setText('yeResultTitle', APP_CONFIG.resultTitle);\n      setText('yeDropMain', APP_CONFIG.dropMain);\n      setText('yeDropSub', APP_CONFIG.dropSub);\n      setText('yeUploadLabel', APP_CONFIG.uploadLabel);\n      setText('yeOutputSizeLabel', APP_CONFIG.outputSizeLabel);\n      setText('yeFitModeLabel', APP_CONFIG.fitModeLabel);\n      setText('yeBgColorLabel', APP_CONFIG.bgColorLabel);\n      setText('yeTipText', APP_CONFIG.tipText);\n      setText('yeFooterText', APP_CONFIG.footerText);\n\n      presetBtn.textContent = APP_CONFIG.presetBtn;\n      resetBtn.textContent = APP_CONFIG.resetBtn;\n      downloadBtn.textContent = APP_CONFIG.downloadBtn;\n      copyBtn.textContent = APP_CONFIG.copyBtn;\n      downloadPreviewBtn.textContent = APP_CONFIG.downloadPreviewBtn;\n\n      APP_CONFIG.sizeOptions.forEach((size) => {\n        const option = document.createElement('option');\n        option.value = size;\n        option.textContent = `${size} \u00d7 ${size}`;\n        if(size === APP_CONFIG.defaults.size) option.selected = true;\n        sizeSelect.appendChild(option);\n      });\n\n      \/* =========================================\n         HELPERS\n      ========================================= *\/\n\n      function updateLabels(){\n        paletteValue.textContent = paletteRange.value;\n        contrastValue.textContent = contrastRange.value;\n        saturationValue.textContent = saturationRange.value;\n        brightnessValue.textContent = brightnessRange.value;\n        zoomValue.textContent = zoomRange.value;\n        offsetXValue.textContent = offsetXRange.value;\n        offsetYValue.textContent = offsetYRange.value;\n        outputMeta.textContent = `${sizeSelect.value} \u00d7 ${sizeSelect.value}`;\n        statSize.textContent = `${sizeSelect.value} \u00d7 ${sizeSelect.value}`;\n        statPalette.textContent = paletteRange.value;\n      }\n\n      function hexToRgb(hex){\n        const clean = hex.replace('#', '');\n        return {\n          r: parseInt(clean.substring(0,2),16),\n          g: parseInt(clean.substring(2,4),16),\n          b: parseInt(clean.substring(4,6),16)\n        };\n      }\n\n      function applyAdjustments(imageData){\n        const data = imageData.data;\n        const contrast = Number(contrastRange.value);\n        const saturation = Number(saturationRange.value) \/ 100;\n        const brightness = Number(brightnessRange.value);\n\n        const factor = (259 * (contrast + 255)) \/ (255 * (259 - contrast));\n\n        for(let i=0;i<data.length;i+=4){\n          let r = data[i];\n          let g = data[i+1];\n          let b = data[i+2];\n\n          r += brightness;\n          g += brightness;\n          b += brightness;\n\n          r = factor * (r - 128) + 128;\n          g = factor * (g - 128) + 128;\n          b = factor * (b - 128) + 128;\n\n          const gray = 0.299*r + 0.587*g + 0.114*b;\n          r = gray + (r - gray) * (1 + saturation);\n          g = gray + (g - gray) * (1 + saturation);\n          b = gray + (b - gray) * (1 + saturation);\n\n          data[i]   = Math.max(0, Math.min(255, Math.round(r)));\n          data[i+1] = Math.max(0, Math.min(255, Math.round(g)));\n          data[i+2] = Math.max(0, Math.min(255, Math.round(b)));\n        }\n\n        return imageData;\n      }\n\n      function quantize(imageData, colorCount){\n        const data = imageData.data;\n        const levels = Math.max(2, Math.round(Math.cbrt(colorCount)));\n        const step = levels <= 1 ? 255 : 255 \/ (levels - 1);\n\n        for(let i=0;i<data.length;i+=4){\n          data[i]   = Math.round(data[i] \/ step) * step;\n          data[i+1] = Math.round(data[i+1] \/ step) * step;\n          data[i+2] = Math.round(data[i+2] \/ step) * step;\n        }\n\n        return imageData;\n      }\n\n      \/* =========================================\n         RENDER\n      ========================================= *\/\n\n      function drawSourceCrop(){\n        const W = sourceCanvas.width;\n        const H = sourceCanvas.height;\n\n        sourceCtx.clearRect(0,0,W,H);\n        sourceCtx.fillStyle = '#0a0d18';\n        sourceCtx.fillRect(0,0,W,H);\n\n        if(!img) return;\n\n        const mode = fitMode.value;\n        const zoom = Number(zoomRange.value) \/ 100;\n        const ox = Number(offsetXRange.value) \/ 100;\n        const oy = Number(offsetYRange.value) \/ 100;\n\n        let drawW, drawH;\n        const imgRatio = img.width \/ img.height;\n        const boxRatio = W \/ H;\n\n        if(mode === 'cover'){\n          if(imgRatio > boxRatio){\n            drawH = H * zoom;\n            drawW = drawH * imgRatio;\n          }else{\n            drawW = W * zoom;\n            drawH = drawW \/ imgRatio;\n          }\n        }else{\n          if(imgRatio > boxRatio){\n            drawW = W * zoom;\n            drawH = drawW \/ imgRatio;\n          }else{\n            drawH = H * zoom;\n            drawW = drawH * imgRatio;\n          }\n\n          const bg = hexToRgb(bgColorInput.value);\n          sourceCtx.fillStyle = `rgb(${bg.r},${bg.g},${bg.b})`;\n          sourceCtx.fillRect(0,0,W,H);\n        }\n\n        const x = (W - drawW) \/ 2 + ox * (W * 0.45);\n        const y = (H - drawH) \/ 2 + oy * (H * 0.45);\n\n        sourceCtx.imageSmoothingEnabled = true;\n        sourceCtx.drawImage(img, x, y, drawW, drawH);\n      }\n\n      function buildPixelArt(){\n        const size = Number(sizeSelect.value);\n        outputCanvas.width = size;\n        outputCanvas.height = size;\n\n        workingCanvas.width = size;\n        workingCanvas.height = size;\n        workingCtx.clearRect(0,0,size,size);\n\n        if(!img){\n          outputCtx.clearRect(0,0,size,size);\n          return;\n        }\n\n        workingCtx.imageSmoothingEnabled = true;\n        workingCtx.drawImage(sourceCanvas, 0, 0, size, size);\n\n        let imageData = workingCtx.getImageData(0,0,size,size);\n        imageData = applyAdjustments(imageData);\n        imageData = quantize(imageData, Number(paletteRange.value));\n        workingCtx.putImageData(imageData, 0, 0);\n\n        outputCtx.imageSmoothingEnabled = false;\n        outputCtx.clearRect(0,0,size,size);\n        outputCtx.drawImage(workingCanvas, 0, 0, size, size);\n      }\n\n      function drawPreview(){\n        const size = Number(sizeSelect.value);\n        const W = previewCanvas.width;\n        const H = previewCanvas.height;\n\n        previewCtx.clearRect(0,0,W,H);\n        previewCtx.fillStyle = '#0a0d18';\n        previewCtx.fillRect(0,0,W,H);\n\n        const pad = 12;\n        const scale = Math.floor((W - pad * 2) \/ size);\n        const drawSize = scale * size;\n        const x = Math.floor((W - drawSize) \/ 2);\n        const y = Math.floor((H - drawSize) \/ 2);\n\n        previewCtx.imageSmoothingEnabled = false;\n        previewCtx.drawImage(outputCanvas, x, y, drawSize, drawSize);\n\n        previewCtx.strokeStyle = 'rgba(255,255,255,.12)';\n        previewCtx.lineWidth = 1;\n\n        for(let i=0;i<=size;i++){\n          const p = x + i * scale;\n          previewCtx.beginPath();\n          previewCtx.moveTo(p, y);\n          previewCtx.lineTo(p, y + drawSize);\n          previewCtx.stroke();\n        }\n\n        for(let i=0;i<=size;i++){\n          const p = y + i * scale;\n          previewCtx.beginPath();\n          previewCtx.moveTo(x, p);\n          previewCtx.lineTo(x + drawSize, p);\n          previewCtx.stroke();\n        }\n      }\n\n      function renderAll(){\n        updateLabels();\n        drawSourceCrop();\n        buildPixelArt();\n        drawPreview();\n      }\n\n      \/* =========================================\n         FILE HANDLING\n      ========================================= *\/\n\n      function handleFile(file){\n        if(!file || !file.type.startsWith('image\/')) return;\n\n        currentFileName = (file.name || 'yoto-image').replace(\/\\.[^.]+$\/, '');\n        const reader = new FileReader();\n\n        reader.onload = (e) => {\n          const image = new Image();\n          image.onload = () => {\n            img = image;\n            sourceMeta.textContent = `${img.width} \u00d7 ${img.height}`;\n            renderAll();\n          };\n          image.src = e.target.result;\n        };\n\n        reader.readAsDataURL(file);\n      }\n\n      \/* =========================================\n         EXPORT\n      ========================================= *\/\n\n      function downloadCanvas(canvas, name){\n        const a = document.createElement('a');\n        a.href = canvas.toDataURL('image\/png');\n        a.download = name;\n        a.click();\n      }\n\n      function exportUpscaled(){\n        const size = Number(sizeSelect.value);\n        const scale = 24;\n        const exportCanvas = document.createElement('canvas');\n        exportCanvas.width = size * scale;\n        exportCanvas.height = size * scale;\n\n        const ctx = exportCanvas.getContext('2d');\n        ctx.imageSmoothingEnabled = false;\n        ctx.drawImage(outputCanvas, 0, 0, exportCanvas.width, exportCanvas.height);\n\n        ctx.strokeStyle = 'rgba(255,255,255,.18)';\n        for(let i=0;i<=size;i++){\n          const p = i * scale;\n          ctx.beginPath();\n          ctx.moveTo(p,0);\n          ctx.lineTo(p,exportCanvas.height);\n          ctx.stroke();\n\n          ctx.beginPath();\n          ctx.moveTo(0,p);\n          ctx.lineTo(exportCanvas.width,p);\n          ctx.stroke();\n        }\n\n        downloadCanvas(exportCanvas, `${currentFileName}-${size}x${size}-preview.png`);\n      }\n\n      async function copyToClipboard(){\n        try{\n          outputCanvas.toBlob(async (blob) => {\n            if(!blob) return;\n            await navigator.clipboard.write([\n              new ClipboardItem({ [blob.type]: blob })\n            ]);\n            copyBtn.textContent = 'Copied';\n            setTimeout(() => {\n              copyBtn.textContent = APP_CONFIG.copyBtn;\n            }, 1200);\n          });\n        }catch(err){\n          alert(\"Clipboard copy is not supported in this browser.\");\n        }\n      }\n\n      \/* =========================================\n         DEFAULTS\n      ========================================= *\/\n\n      function applyDefaults(){\n        sizeSelect.value = APP_CONFIG.defaults.size;\n        fitMode.value = APP_CONFIG.defaults.fitMode;\n        paletteRange.value = APP_CONFIG.defaults.palette;\n        contrastRange.value = APP_CONFIG.defaults.contrast;\n        saturationRange.value = APP_CONFIG.defaults.saturation;\n        brightnessRange.value = APP_CONFIG.defaults.brightness;\n        zoomRange.value = APP_CONFIG.defaults.zoom;\n        offsetXRange.value = APP_CONFIG.defaults.offsetX;\n        offsetYRange.value = APP_CONFIG.defaults.offsetY;\n        bgColorInput.value = APP_CONFIG.defaults.bgColor;\n      }\n\n      \/* =========================================\n         EVENTS\n      ========================================= *\/\n\n      app.querySelectorAll('input, select').forEach(el => {\n        el.addEventListener('input', renderAll);\n        el.addEventListener('change', renderAll);\n      });\n\n      presetBtn.addEventListener('click', () => {\n        applyDefaults();\n        renderAll();\n      });\n\n      resetBtn.addEventListener('click', () => {\n        applyDefaults();\n        renderAll();\n      });\n\n      downloadBtn.addEventListener('click', () => {\n        const size = Number(sizeSelect.value);\n        downloadCanvas(outputCanvas, `${currentFileName}-${size}x${size}.png`);\n      });\n\n      downloadPreviewBtn.addEventListener('click', exportUpscaled);\n      copyBtn.addEventListener('click', copyToClipboard);\n\n      fileInput.addEventListener('change', e => handleFile(e.target.files[0]));\n\n      ['dragenter','dragover'].forEach(ev => {\n        dropZone.addEventListener(ev, e => {\n          e.preventDefault();\n          dropZone.classList.add('drag');\n        });\n      });\n\n      ['dragleave','drop'].forEach(ev => {\n        dropZone.addEventListener(ev, e => {\n          e.preventDefault();\n          dropZone.classList.remove('drag');\n        });\n      });\n\n      dropZone.addEventListener('drop', e => {\n        const file = e.dataTransfer.files[0];\n        handleFile(file);\n      });\n\n      \/* =========================================\n         INIT\n      ========================================= *\/\n\n      applyDefaults();\n      updateLabels();\n      renderAll();\n    })();\n  <\/script>\n<\/div>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Create a Yoto Card Upload an image or take a photo, turn it into pixel art, add a title, and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"disabled","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"class_list":["post-18","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/pages\/18","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/comments?post=18"}],"version-history":[{"count":19,"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/pages\/18\/revisions"}],"predecessor-version":[{"id":53,"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/pages\/18\/revisions\/53"}],"wp:attachment":[{"href":"https:\/\/yotoimages.com\/fr\/wp-json\/wp\/v2\/media?parent=18"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}