Where you can achieve what your competitors can’t


Custom Product Builder in Shopify

This custom code is completely free to use, but please note it does not include 1-on-1 technical support. If you need a bespoke, done-for-you solution to customize your Shopify store, my team is here to help! Head to my channel homepage and use the link in the trailer to reach out.

{% comment %}
<!-- Designed by ONHOW Studio - Anas El Medlaoui -->
{% endcomment %}

{%- assign onhow_studio_file_accept = 'image/*' -%}
{%- if section.settings.onhow_studio_file_mode == 'pdf' -%}
  {%- assign onhow_studio_file_accept = 'application/pdf' -%}
{%- elsif section.settings.onhow_studio_file_mode == 'all' -%}
  {%- assign onhow_studio_file_accept = '*' -%}
{%- endif -%}

<div
  id="onhow-studio-upload-config"
  data-section-id="{{ section.id }}"
  data-mode="{{ section.settings.onhow_studio_file_mode }}"
  data-accept="{{ onhow_studio_file_accept }}"
  data-label="{{ section.settings.onhow_studio_property_label | escape }}"
  data-max-size="{{ section.settings.onhow_studio_max_file_size }}"
  data-btn-text="{{ section.settings.onhow_studio_btn_text | escape }}"
  data-helper-text="{{ section.settings.onhow_studio_helper_text | escape }}"
  data-error-type="{{ section.settings.onhow_studio_error_type | escape }}"
  data-error-size="{{ section.settings.onhow_studio_error_size | escape }}"
  data-primary="{{ section.settings.onhow_studio_primary_color }}"
  data-bg="{{ section.settings.onhow_studio_upload_bg_color }}"
  data-radius="{{ section.settings.onhow_studio_border_radius }}"
  data-border-style="{{ section.settings.onhow_studio_border_style }}"
  data-text-input="{{ section.settings.onhow_studio_enable_text_input }}"
  data-text-label="{{ section.settings.onhow_studio_text_input_label | escape }}"
  data-text-input2="{{ section.settings.onhow_studio_enable_text_input_2 }}"
  data-text-label2="{{ section.settings.onhow_studio_text_input_2_label | escape }}"
  data-toggle-enabled="{{ section.settings.onhow_studio_enable_toggle }}"
  data-toggle-key="{{ section.settings.onhow_studio_toggle_property_key | escape }}"
  data-toggle-label="{{ section.settings.onhow_studio_toggle_label | escape }}"
  hidden
></div>

{% schema %}
{
  "name": "OnHOW Upload Config",
  "settings": [
    {
      "type": "header",
      "content": "✨ Built by ONHOW Studio"
    },
    {
      "type": "paragraph",
      "content": "Get free sections & tutorials at youtube.com/@ONHOWStudio"
    },
    {
      "type": "text",
      "id": "onhow_studio_core_logic",
      "label": "Section Architecture (Do Not Edit)",
      "default": "built-by-onhow-studio",
      "info": "Modifying this will break the section's CSS layout."
    },
    {
      "type": "header",
      "content": "Functional"
    },
    {
      "type": "select",
      "id": "onhow_studio_file_mode",
      "label": "File Type",
      "options": [
        { "value": "image", "label": "Image only" },
        { "value": "pdf", "label": "PDF only" },
        { "value": "all", "label": "All files" }
      ],
      "default": "image"
    },
    {
      "type": "text",
      "id": "onhow_studio_property_label",
      "label": "Property Label",
      "default": "Upload File",
      "info": "Appears as the line item property name on the order. Must be unique across all enabled fields in this section."
    },
    {
      "type": "range",
      "id": "onhow_studio_max_file_size",
      "label": "Max File Size (MB)",
      "min": 1,
      "max": 50,
      "step": 1,
      "default": 10
    },
    {
      "type": "header",
      "content": "UI Text"
    },
    {
      "type": "text",
      "id": "onhow_studio_btn_text",
      "label": "Browse Button Text",
      "default": "Browse Files"
    },
    {
      "type": "text",
      "id": "onhow_studio_helper_text",
      "label": "Helper Text",
      "default": "Drag & drop or click to upload"
    },
    {
      "type": "text",
      "id": "onhow_studio_error_type",
      "label": "Wrong File Type Message",
      "default": "Invalid file type."
    },
    {
      "type": "text",
      "id": "onhow_studio_error_size",
      "label": "File Too Large Message",
      "default": "File exceeds the maximum size."
    },
    {
      "type": "header",
      "content": "Text Input Field 1"
    },
    {
      "type": "checkbox",
      "id": "onhow_studio_enable_text_input",
      "label": "Enable text input field",
      "default": false
    },
    {
      "type": "text",
      "id": "onhow_studio_text_input_label",
      "label": "Text Input Label",
      "default": "Special Instructions",
      "info": "Also used as the line item property name on the order. Must be unique across all enabled fields in this section."
    },
    {
      "type": "header",
      "content": "Text Input Field 2"
    },
    {
      "type": "checkbox",
      "id": "onhow_studio_enable_text_input_2",
      "label": "Enable text input field",
      "default": false
    },
    {
      "type": "text",
      "id": "onhow_studio_text_input_2_label",
      "label": "Text Input Label",
      "default": "Additional Notes",
      "info": "Also used as the line item property name on the order. Must be unique across all enabled fields in this section."
    },
    {
      "type": "header",
      "content": "Toggle Option"
    },
    {
      "type": "checkbox",
      "id": "onhow_studio_enable_toggle",
      "label": "Enable toggle option",
      "default": false
    },
    {
      "type": "text",
      "id": "onhow_studio_toggle_property_key",
      "label": "Property Name",
      "default": "Gift Wrapping",
      "info": "Line item property name on the order. Must be unique across all enabled fields in this section."
    },
    {
      "type": "text",
      "id": "onhow_studio_toggle_label",
      "label": "Toggle Label",
      "default": "I want a gift wrapper",
      "info": "Visible text next to the checkbox. When checked, the value submitted is 'Yes, <this text>'. When unchecked, no property is sent."
    },
    {
      "type": "header",
      "content": "Visual"
    },
    {
      "type": "color",
      "id": "onhow_studio_primary_color",
      "label": "Primary Color",
      "default": "#000000"
    },
    {
      "type": "color",
      "id": "onhow_studio_upload_bg_color",
      "label": "Upload Zone Background",
      "default": "#f9f9f9"
    },
    {
      "type": "range",
      "id": "onhow_studio_border_radius",
      "label": "Border Radius (px)",
      "min": 0,
      "max": 20,
      "step": 1,
      "default": 8
    },
    {
      "type": "select",
      "id": "onhow_studio_border_style",
      "label": "Border Style",
      "options": [
        { "value": "dashed", "label": "Dashed" },
        { "value": "solid", "label": "Solid" }
      ],
      "default": "dashed"
    }
  ],
  "presets": [
    {
      "name": "OnHOW Upload Config (By ONHOW Studio)"
    }
  ]
}
{% endschema %}

{% comment %}
<!-- Designed by ONHOW Studio - Anas El Medlaoui -->
{% endcomment %}






Code block :

{% comment %}
<!-- Designed by ONHOW Studio - Anas El Medlaoui -->
{% endcomment %}

<div class="onhow-studio-upload-wrapper" data-block-id="{{ block.id }}">
  <div class="onhow-studio-upload-text-group onhow-studio-upload-hidden">
    <label class="onhow-studio-upload-text-label" for="onhow-studio-upload-text-input-{{ block.id }}"></label>
    <input type="text" id="onhow-studio-upload-text-input-{{ block.id }}" class="onhow-studio-upload-text-input" autocomplete="off">
  </div>

  <div class="onhow-studio-upload-text-group onhow-studio-upload-text-group-2 onhow-studio-upload-hidden">
    <label class="onhow-studio-upload-text-label" for="onhow-studio-upload-text-input-2-{{ block.id }}"></label>
    <input type="text" id="onhow-studio-upload-text-input-2-{{ block.id }}" class="onhow-studio-upload-text-input" autocomplete="off">
  </div>

  <label class="onhow-studio-upload-toggle-group onhow-studio-upload-hidden">
    <input type="checkbox" id="onhow-studio-upload-toggle-input-{{ block.id }}" class="onhow-studio-upload-toggle-input">
    <span class="onhow-studio-upload-toggle-box">
      <svg class="onhow-studio-upload-toggle-check" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <polyline points="20 6 9 17 4 12"></polyline>
      </svg>
    </span>
    <span class="onhow-studio-upload-toggle-label-text"></span>
  </label>

  <label class="onhow-studio-upload-label" for="onhow-studio-upload-input-{{ block.id }}"></label>

  <div class="onhow-studio-upload-zone">
    <input
      type="file"
      id="onhow-studio-upload-input-{{ block.id }}"
      class="onhow-studio-upload-input"
    >
    <svg class="onhow-studio-upload-icon" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <polyline points="16 16 12 12 8 16"></polyline>
      <line x1="12" y1="12" x2="12" y2="21"></line>
      <path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path>
    </svg>
    <span class="onhow-studio-upload-hint"></span>
    <button type="button" class="onhow-studio-upload-btn"></button>
  </div>

  <div class="onhow-studio-upload-preview onhow-studio-upload-hidden">
    <img class="onhow-studio-upload-img" src="" alt="Uploaded image preview">
    <div class="onhow-studio-upload-card">
      <svg class="onhow-studio-upload-card-icon" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
        <polyline points="14 2 14 8 20 8"></polyline>
      </svg>
      <div class="onhow-studio-upload-card-info">
        <span class="onhow-studio-upload-card-name"></span>
        <span class="onhow-studio-upload-card-size"></span>
      </div>
    </div>
    <button type="button" class="onhow-studio-upload-remove" aria-label="Remove file">
      <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <line x1="18" y1="6" x2="6" y2="18"></line>
        <line x1="6" y1="6" x2="18" y2="18"></line>
      </svg>
    </button>
  </div>

  <p class="onhow-studio-upload-error onhow-studio-upload-hidden" role="alert"></p>
</div>

<style>
  .onhow-studio-upload-wrapper {
    --onhow-studio-upload-primary: #000000;
    --onhow-studio-upload-bg: #f9f9f9;
    --onhow-studio-upload-radius: 8px;
    --onhow-studio-upload-border-style: dashed;
    display: block;
    width: 100%;
    font-family: inherit;
    margin-block: 0;
  }

  .onhow-studio-upload-label {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
    color: currentColor;
  }

  .onhow-studio-upload-zone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 1.25rem 1.5rem;
    border: 2px var(--onhow-studio-upload-border-style) rgba(0, 0, 0, 0.2);
    border-radius: var(--onhow-studio-upload-radius);
    background-color: var(--onhow-studio-upload-bg);
    cursor: pointer;
    transition: border-color 0.2s ease, background-color 0.2s ease;
  }

  .onhow-studio-upload-zone:hover {
    border-color: var(--onhow-studio-upload-primary);
  }

  .onhow-studio-upload-zone.onhow-studio-upload-drag-over {
    border-color: var(--onhow-studio-upload-primary);
    background-color: var(--onhow-studio-upload-bg);
    opacity: 0.75;
  }

  .onhow-studio-upload-input {
    display: none;
  }

  .onhow-studio-upload-icon {
    color: var(--onhow-studio-upload-primary);
    opacity: 0.5;
  }

  .onhow-studio-upload-hint {
    font-size: 0.8125rem;
    color: currentColor;
    opacity: 0.55;
    text-align: center;
  }

  .onhow-studio-upload-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.4375rem 1.125rem;
    margin-top: 0.25rem;
    font-size: 0.8125rem;
    font-weight: 600;
    font-family: inherit;
    border: 1.5px solid var(--onhow-studio-upload-primary);
    border-radius: var(--onhow-studio-upload-radius);
    background: transparent;
    color: var(--onhow-studio-upload-primary);
    cursor: pointer;
    transition: background-color 0.2s ease, color 0.2s ease;
  }

  .onhow-studio-upload-btn:hover {
    background-color: var(--onhow-studio-upload-primary);
    color: #ffffff;
  }

  .onhow-studio-upload-preview {
    position: relative;
    width: 100%;
    border-radius: var(--onhow-studio-upload-radius);
    overflow: hidden;
  }

  .onhow-studio-upload-img {
    display: block;
    width: 100%;
    height: auto;
    max-height: 280px;
    object-fit: contain;
    border-radius: var(--onhow-studio-upload-radius);
    background-color: var(--onhow-studio-upload-bg);
  }

  .onhow-studio-upload-card {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 1rem;
    border: 2px var(--onhow-studio-upload-border-style) var(--onhow-studio-upload-primary);
    border-radius: var(--onhow-studio-upload-radius);
    background-color: var(--onhow-studio-upload-bg);
  }

  .onhow-studio-upload-card-icon {
    color: var(--onhow-studio-upload-primary);
    flex-shrink: 0;
  }

  .onhow-studio-upload-card-info {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    min-width: 0;
    flex: 1;
  }

  .onhow-studio-upload-card-name {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    color: currentColor;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .onhow-studio-upload-card-size {
    display: block;
    font-size: 0.75rem;
    opacity: 0.55;
    color: currentColor;
  }

  .onhow-studio-upload-remove {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: none;
    border-radius: 50%;
    background-color: rgba(0, 0, 0, 0.6);
    color: #ffffff;
    cursor: pointer;
    transition: background-color 0.2s ease;
  }

  .onhow-studio-upload-remove:hover {
    background-color: rgba(0, 0, 0, 0.85);
  }

  .onhow-studio-upload-error {
    margin-top: 0.5rem;
    margin-bottom: 0;
    font-size: 0.8125rem;
    color: #c0392b;
  }

  .onhow-studio-upload-wrapper .onhow-studio-upload-hidden {
    display: none;
  }

  .onhow-studio-upload-text-group {
    margin-bottom: 0.75rem;
  }

  .onhow-studio-upload-text-label {
    display: block;
    font-size: 0.875rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
    color: currentColor;
  }

  .onhow-studio-upload-wrapper .onhow-studio-upload-text-input {
    display: block;
    width: 100%;
    padding: 0.625rem 0.75rem;
    font-size: 0.875rem;
    font-family: inherit;
    color: currentColor;
    background: transparent;
    border: 1.5px solid rgba(0, 0, 0, 0.2);
    border-radius: var(--onhow-studio-upload-radius);
    outline: none;
    box-sizing: border-box;
    transition: border-color 0.2s ease;
    -webkit-appearance: none;
    appearance: none;
  }

  .onhow-studio-upload-wrapper .onhow-studio-upload-text-input:focus {
    border-color: var(--onhow-studio-upload-primary);
  }

  .onhow-studio-upload-wrapper .onhow-studio-upload-text-input::placeholder {
    opacity: 0.4;
  }

  .onhow-studio-upload-toggle-group {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.75rem;
    cursor: pointer;
    user-select: none;
  }

  .onhow-studio-upload-wrapper .onhow-studio-upload-toggle-input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    width: 0;
    height: 0;
    margin: 0;
  }

  .onhow-studio-upload-toggle-box {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    border: 1.5px solid rgba(0, 0, 0, 0.3);
    border-radius: var(--onhow-studio-upload-radius);
    background-color: transparent;
    transition: background-color 0.2s ease, border-color 0.2s ease;
  }

  .onhow-studio-upload-toggle-check {
    width: 12px;
    height: 12px;
    color: #ffffff;
    opacity: 0;
    transition: opacity 0.15s ease;
  }

  .onhow-studio-upload-toggle-group:hover .onhow-studio-upload-toggle-box {
    border-color: var(--onhow-studio-upload-primary);
  }

  .onhow-studio-upload-toggle-input:checked ~ .onhow-studio-upload-toggle-box {
    background-color: var(--onhow-studio-upload-primary);
    border-color: var(--onhow-studio-upload-primary);
  }

  .onhow-studio-upload-toggle-input:checked ~ .onhow-studio-upload-toggle-box .onhow-studio-upload-toggle-check {
    opacity: 1;
  }

  .onhow-studio-upload-toggle-input:focus-visible ~ .onhow-studio-upload-toggle-box {
    outline: 2px solid var(--onhow-studio-upload-primary);
    outline-offset: 2px;
  }

  .onhow-studio-upload-toggle-label-text {
    font-size: 0.875rem;
    font-weight: 600;
    color: currentColor;
  }
</style>

<script>
  document.addEventListener('DOMContentLoaded', function () {
    (function() {
      var onhowStudioCredit = atob("RGVzaWduZWQgYnkgT05IT1cgU3R1ZGlvIC0gU3Vic2NyaWJlIQ==");
      console.log('%c ' + onhowStudioCredit, 'background: #222; color: #00ff00; padding: 4px; border-radius: 4px;');
    })();

    var config = document.getElementById('onhow-studio-upload-config');
    if (!config) return;

    var wrappers = document.querySelectorAll('.onhow-studio-upload-wrapper');

    wrappers.forEach(function (wrapper) {
      if (wrapper.getAttribute('data-onhow-initialized') === 'true') return;
      wrapper.setAttribute('data-onhow-initialized', 'true');
      onhowStudioInit(wrapper, config);
    });

    function onhowStudioInit(wrapper, config) {
      var blockId = wrapper.getAttribute('data-block-id') || 'fallback';
      var productForm = document.querySelector('form[action*="/cart/add"]');
      if (!productForm) return;

      var mode         = config.dataset.mode;
      var accept       = config.dataset.accept;
      var label        = config.dataset.label;
      var maxSize      = parseInt(config.dataset.maxSize, 10);
      var btnText      = config.dataset.btnText;
      var helperText   = config.dataset.helperText;
      var errorType    = config.dataset.errorType;
      var errorSize    = config.dataset.errorSize;
      var textEnabled  = config.dataset.textInput === 'true';
      var textLabel    = config.dataset.textLabel;
      var text2Enabled = config.dataset.textInput2 === 'true';
      var textLabel2   = config.dataset.textLabel2;
      var toggleEnabled = config.dataset.toggleEnabled === 'true';
      var toggleKey    = config.dataset.toggleKey;
      var toggleLabel  = config.dataset.toggleLabel;

      var usedNames = {};
      function claim(name) {
        if (!name) return false;
        if (usedNames[name]) return false;
        usedNames[name] = true;
        return true;
      }
      var canUseFile   = claim(label);
      var canUseText1  = textEnabled && claim(textLabel);
      var canUseText2  = text2Enabled && claim(textLabel2);
      var canUseToggle = toggleEnabled && toggleKey && toggleLabel && claim(toggleKey);

      if (!canUseFile) return;

      var hiddenIdFile   = 'onhow-studio-h-file-' + blockId;
      var hiddenIdText1  = 'onhow-studio-h-text1-' + blockId;
      var hiddenIdText2  = 'onhow-studio-h-text2-' + blockId;
      var hiddenIdToggle = 'onhow-studio-h-toggle-' + blockId;

      wrapper.style.setProperty('--onhow-studio-upload-primary', config.dataset.primary);
      wrapper.style.setProperty('--onhow-studio-upload-bg', config.dataset.bg);
      wrapper.style.setProperty('--onhow-studio-upload-radius', config.dataset.radius + 'px');
      wrapper.style.setProperty('--onhow-studio-upload-border-style', config.dataset.borderStyle);

      var labelEl   = wrapper.querySelector('.onhow-studio-upload-label');
      var zone      = wrapper.querySelector('.onhow-studio-upload-zone');
      var input     = wrapper.querySelector('.onhow-studio-upload-input');
      var hint      = wrapper.querySelector('.onhow-studio-upload-hint');
      var btn       = wrapper.querySelector('.onhow-studio-upload-btn');
      var preview   = wrapper.querySelector('.onhow-studio-upload-preview');
      var imgEl     = wrapper.querySelector('.onhow-studio-upload-img');
      var card      = wrapper.querySelector('.onhow-studio-upload-card');
      var cardName  = wrapper.querySelector('.onhow-studio-upload-card-name');
      var cardSize  = wrapper.querySelector('.onhow-studio-upload-card-size');
      var removeBtn = wrapper.querySelector('.onhow-studio-upload-remove');
      var errorEl   = wrapper.querySelector('.onhow-studio-upload-error');

      function syncTextHidden(inputEl, hiddenId, propName) {
        var val = inputEl.value;
        var existing = document.getElementById(hiddenId);
        if (val) {
          if (!existing) {
            existing = document.createElement('input');
            existing.type = 'hidden';
            existing.id = hiddenId;
            existing.name = 'properties[' + propName + ']';
            productForm.appendChild(existing);
          }
          existing.value = val;
        } else if (existing) {
          existing.remove();
        }
      }

      if (canUseText1) {
        var textGroup1   = wrapper.querySelector('.onhow-studio-upload-text-group:not(.onhow-studio-upload-text-group-2)');
        var textLabelEl1 = textGroup1.querySelector('.onhow-studio-upload-text-label');
        var textInput1   = textGroup1.querySelector('.onhow-studio-upload-text-input');

        textLabelEl1.textContent = textLabel;
        textInput1.placeholder   = textLabel;
        textGroup1.classList.remove('onhow-studio-upload-hidden');

        textInput1.addEventListener('input', function () {
          syncTextHidden(textInput1, hiddenIdText1, textLabel);
        });
      }

      if (canUseText2) {
        var textGroup2   = wrapper.querySelector('.onhow-studio-upload-text-group-2');
        var textLabelEl2 = textGroup2.querySelector('.onhow-studio-upload-text-label');
        var textInput2   = textGroup2.querySelector('.onhow-studio-upload-text-input');

        textLabelEl2.textContent = textLabel2;
        textInput2.placeholder   = textLabel2;
        textGroup2.classList.remove('onhow-studio-upload-hidden');

        textInput2.addEventListener('input', function () {
          syncTextHidden(textInput2, hiddenIdText2, textLabel2);
        });
      }

      if (canUseToggle) {
        var toggleGroup   = wrapper.querySelector('.onhow-studio-upload-toggle-group');
        var toggleInput   = wrapper.querySelector('.onhow-studio-upload-toggle-input');
        var toggleLabelEl = wrapper.querySelector('.onhow-studio-upload-toggle-label-text');

        toggleLabelEl.textContent = toggleLabel;
        toggleGroup.classList.remove('onhow-studio-upload-hidden');

        toggleInput.addEventListener('change', function () {
          var existing = document.getElementById(hiddenIdToggle);
          if (toggleInput.checked) {
            if (!existing) {
              existing = document.createElement('input');
              existing.type = 'hidden';
              existing.id = hiddenIdToggle;
              existing.name = 'properties[' + toggleKey + ']';
              productForm.appendChild(existing);
            }
            existing.value = 'Yes, ' + toggleLabel;
          } else if (existing) {
            existing.remove();
          }
        });
      }

      labelEl.textContent = label;
      hint.textContent    = helperText;
      btn.textContent     = btnText;

      input.setAttribute('accept', accept);

      if (mode === 'image') {
        card.classList.add('onhow-studio-upload-hidden');
      } else {
        imgEl.classList.add('onhow-studio-upload-hidden');
      }

      function formatSize(bytes) {
        if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
      }

      function showError(msg) {
        errorEl.textContent = msg;
        errorEl.classList.remove('onhow-studio-upload-hidden');
      }

      function clearError() {
        errorEl.textContent = '';
        errorEl.classList.add('onhow-studio-upload-hidden');
      }

      function clearPreview() {
        removeHiddenInput();
        preview.classList.add('onhow-studio-upload-hidden');
        zone.classList.remove('onhow-studio-upload-hidden');
        imgEl.src = '';
        cardName.textContent = '';
        cardSize.textContent = '';
        input.value = '';
      }

      function validateFile(file) {
        if (accept !== '*') {
          var types = accept.split(',').map(function (t) { return t.trim(); });
          var valid = types.some(function (t) {
            if (t.slice(-2) === '/*') return file.type.startsWith(t.slice(0, -2));
            return file.type === t;
          });
          if (!valid) { showError(errorType); return false; }
        }
        if (file.size > maxSize * 1024 * 1024) { showError(errorSize); return false; }
        return true;
      }

      function injectHiddenInput(file) {
        var existing = document.getElementById(hiddenIdFile);
        if (existing) existing.remove();
        var hidden = document.createElement('input');
        hidden.type = 'file';
        hidden.id   = hiddenIdFile;
        hidden.name = 'properties[' + label + ']';
        hidden.style.display = 'none';
        var dt = new DataTransfer();
        dt.items.add(file);
        hidden.files = dt.files;
        productForm.appendChild(hidden);
      }

      function removeHiddenInput() {
        var existing = document.getElementById(hiddenIdFile);
        if (existing) existing.remove();
      }

      function handleFile(file) {
        clearError();
        if (!validateFile(file)) { clearPreview(); return; }

        injectHiddenInput(file);

        if (mode === 'image') {
          var reader = new FileReader();
          reader.onload = function (e) {
            imgEl.src = e.target.result;
            zone.classList.add('onhow-studio-upload-hidden');
            preview.classList.remove('onhow-studio-upload-hidden');
          };
          reader.readAsDataURL(file);
        } else {
          cardName.textContent = file.name;
          cardSize.textContent = formatSize(file.size);
          zone.classList.add('onhow-studio-upload-hidden');
          preview.classList.remove('onhow-studio-upload-hidden');
        }
      }

      input.addEventListener('change', function () {
        if (this.files[0]) handleFile(this.files[0]);
      });

      removeBtn.addEventListener('click', clearPreview);

      zone.addEventListener('click', function () {
        input.click();
      });

      btn.addEventListener('click', function (e) {
        e.stopPropagation();
        input.click();
      });

      zone.addEventListener('dragover', function (e) {
        e.preventDefault();
        zone.classList.add('onhow-studio-upload-drag-over');
      });

      zone.addEventListener('dragleave', function () {
        zone.classList.remove('onhow-studio-upload-drag-over');
      });

      zone.addEventListener('drop', function (e) {
        e.preventDefault();
        zone.classList.remove('onhow-studio-upload-drag-over');
        var file = e.dataTransfer.files[0];
        if (!file) return;
        var dt = new DataTransfer();
        dt.items.add(file);
        input.files = dt.files;
        handleFile(file);
      });
    }
  });
</script>

{% comment %}
<!-- Designed by ONHOW Studio - Anas El Medlaoui -->
{% endcomment %}