Loading editor/html/js/editor.js +13 −3 Original line number Diff line number Diff line Loading @@ -2541,7 +2541,7 @@ // Fetches the project list; shared by initial load and the project manager dialog // (loadConnectionList) so projectsCache stays in sync after add/edit/delete. function loadProjectSelectOptions(callback) { EditorApi.listConnections().then(function(resp) { return EditorApi.listConnections().then(function(resp) { projectsCache = resp.connections || []; if (callback) callback(); }); Loading Loading @@ -2734,8 +2734,14 @@ status.className = 'set-status success'; var savedId = resp.id || conn.id; document.getElementById('conn-edit-id').value = savedId || ''; loadConnectionList(); // onProjectSelected() reads the newly saved project out of // projectsCache, so it must wait for loadConnectionList()'s // refetch to resolve -- calling it synchronously right after // loadConnectionList() ran against the still-stale cache and // blanked out the form the user just filled in/saved. loadConnectionList().then(function() { if (savedId) onProjectSelected(savedId); }); setTimeout(function() { status.textContent = ''; status.className = 'set-status'; }, 2000); }).catch(function(err) { status.textContent = I18n.t('I18N_SAVE_FAILED', 'Speichern fehlgeschlagen') + ': ' + (err.error || ''); Loading Loading @@ -2985,8 +2991,12 @@ } function loadConnectionList() { loadProjectSelectOptions(function() { return loadProjectSelectOptions(function() { renderConnectionList(projectsCache); // renderConnectionList() rebuilds #conn-list from scratch, so the // selection highlight and settings-form fields need reapplying // every time this runs, not just on the very first load. syncProjectManagerModalUI(); }); } Loading editor/widgets/container/container.cpp +36 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,9 @@ extern "C" { std::string m_boxShadowColor = ""; std::string m_boxShadowCustom = ""; // Hover effect: lifts the container upward on hover (desktop only) bool hoverLift = false; // Visibility bool hidden = false; bool m_hidden = false; Loading Loading @@ -367,6 +370,11 @@ extern "C" { addField("box_shadow", "Box Shadow", "select", nullptr, "desktop", mkOpts({"none","sm","md","lg","card","float","inner"})); addField("box_shadow_color", "Box Shadow Color", "color_var", nullptr, "desktop"); addField("box_shadow_custom", "Box Shadow Custom", "text", "e.g. 0 4px 20px rgba(0,0,0,0.5)", "desktop"); addField("hover_lift", "Hover Lift", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *hlf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); json_object_object_add(hlf, "default", json_object_new_boolean(false)); } addField("hidden", "Hidden", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *hf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); Loading Loading @@ -507,6 +515,8 @@ extern "C" { element->SetAttribute("m_box_shadow_color", m_boxShadowColor.c_str()); element->SetAttribute("m_box_shadow_custom", m_boxShadowCustom.c_str()); element->SetAttribute("hover_lift", hoverLift ? "true" : "false"); // Visibility element->SetAttribute("hidden", hidden ? "true" : "false"); element->SetAttribute("m_hidden", m_hidden ? "true" : "false"); Loading Loading @@ -705,6 +715,9 @@ extern "C" { const char* loaded_m_bsh_custom = xml_data->Attribute("m_box_shadow_custom"); if (loaded_m_bsh_custom) m_boxShadowCustom = loaded_m_bsh_custom; const char* loaded_hover_lift = xml_data->Attribute("hover_lift"); if (loaded_hover_lift) hoverLift = (strcmp(loaded_hover_lift, "true") == 0); // Visibility const char* loaded_hidden = xml_data->Attribute("hidden"); if (loaded_hidden) hidden = (strcmp(loaded_hidden, "true") == 0); Loading Loading @@ -792,6 +805,7 @@ extern "C" { json_object_object_add(obj, "m_box_shadow", json_object_new_string(m_boxShadow.c_str())); json_object_object_add(obj, "m_box_shadow_color", json_object_new_string(m_boxShadowColor.c_str())); json_object_object_add(obj, "m_box_shadow_custom", json_object_new_string(m_boxShadowCustom.c_str())); json_object_object_add(obj, "hover_lift", json_object_new_boolean(hoverLift)); json_object_object_add(obj, "hidden", json_object_new_boolean(hidden)); json_object_object_add(obj, "m_hidden", json_object_new_boolean(m_hidden)); json_object_object_add(obj, "custom_css", json_object_new_string(customCss.c_str())); Loading Loading @@ -1067,6 +1081,14 @@ extern "C" { val = json_object_get_string(val_obj); if (val) m_boxShadowCustom = val; } if (json_object_object_get_ex(obj, "hover_lift", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); hoverLift = (s && (strcmp(s, "true") == 0 || strcmp(s, "1") == 0)); } else { hoverLift = json_object_get_boolean(val_obj); } } if (json_object_object_get_ex(obj, "hidden", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); Loading Loading @@ -1184,6 +1206,9 @@ extern "C" { if (hasShapeDivider) { style += " position: relative;"; } if (hoverLift) { style += " transition: transform 0.25s ease, box-shadow 0.25s ease;"; } if (hidden) { style += " display: none;"; } Loading Loading @@ -1355,6 +1380,17 @@ extern "C" { el.appendChild(mobileLightStyle); } // Hover lift: nudges the container upward with a soft shadow on // hover. Desktop-only (matches the mobile breakpoint used above) // since :hover has no reliable touch equivalent. if (hoverLift) { libhtmlpp::HtmlElement hoverStyle("style"); std::string hoverRule = "@media (min-width: 769px) { ." + responsiveClass + ":hover { transform: translateY(-8px); box-shadow: 0 20px 45px rgba(0,0,0,0.15); } }"; hoverStyle.appendChild(libhtmlpp::TextElement(hoverRule.c_str())); el.appendChild(hoverStyle); } if (!customCss.empty()) { libhtmlpp::HtmlElement cssStyle("style"); // Emit raw — user writes full selectors, e.g. .we-xxx { color: red; } Loading editor/widgets/container/translations.h +1 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ namespace blogi { namespace webedit { namespace container_i18n { {"Gradient Custom", "Eigener Farbverlauf"}, {"Height", "Höhe"}, {"Hidden", "Ausgeblendet"}, {"Hover Lift", "Hover-Anhebung"}, {"Margin", "Außenabstand"}, {"Mobile Background Color", "Hintergrundfarbe (Mobil)"}, {"Mobile Background Color (Light)", "Hintergrundfarbe (Mobil Hell)"}, Loading editor/widgets/image/image.cpp +35 −0 Original line number Diff line number Diff line Loading @@ -83,6 +83,10 @@ extern "C" { // Fullscreen bool fullscreen = false; // Hover zoom: "in" grows the image on hover, "out" starts the // image zoomed in and settles back to normal size on hover std::string hoverZoom = "none"; // Custom CSS std::string customCss = ""; Loading Loading @@ -284,6 +288,7 @@ extern "C" { element->SetAttribute("object_fit", objectFit.c_str()); element->SetAttribute("border_radius", borderRadius.c_str()); element->SetAttribute("border_style", borderStyle.c_str()); element->SetAttribute("hover_zoom", hoverZoom.c_str()); element->SetAttribute("link_url", linkUrl.c_str()); element->SetAttribute("media_id", mediaId.c_str()); element->SetAttribute("media_ext", mediaExt.c_str()); Loading Loading @@ -339,6 +344,7 @@ extern "C" { val = xml_data->Attribute("object_fit"); if (val) objectFit = val; val = xml_data->Attribute("border_radius"); if (val) borderRadius = val; val = xml_data->Attribute("border_style"); if (val) borderStyle = val; val = xml_data->Attribute("hover_zoom"); if (val) hoverZoom = val; val = xml_data->Attribute("link_url"); if (val) linkUrl = val; val = xml_data->Attribute("media_id"); if (val) mediaId = val; val = xml_data->Attribute("media_ext"); if (val) mediaExt = val; Loading Loading @@ -398,6 +404,7 @@ extern "C" { json_object_object_add(obj, "object_fit", json_object_new_string(objectFit.c_str())); json_object_object_add(obj, "border_radius", json_object_new_string(borderRadius.c_str())); json_object_object_add(obj, "border_style", json_object_new_string(borderStyle.c_str())); json_object_object_add(obj, "hover_zoom", json_object_new_string(hoverZoom.c_str())); json_object_object_add(obj, "link_url", json_object_new_string(linkUrl.c_str())); json_object_object_add(obj, "media_id", json_object_new_string(mediaId.c_str())); json_object_object_add(obj, "media_ext", json_object_new_string(mediaExt.c_str())); Loading Loading @@ -459,6 +466,9 @@ extern "C" { if (json_object_object_get_ex(obj, "border_style", &val_obj)) { val = json_object_get_string(val_obj); if (val) borderStyle = val; } if (json_object_object_get_ex(obj, "hover_zoom", &val_obj)) { val = json_object_get_string(val_obj); if (val) hoverZoom = val; } if (json_object_object_get_ex(obj, "link_url", &val_obj)) { val = json_object_get_string(val_obj); if (val) linkUrl = val; } Loading Loading @@ -623,6 +633,7 @@ extern "C" { addField("border_radius", "Border Radius", "text", "0"); addField("border_style", "Border", "text", "e.g. 1px solid #ccc", "desktop"); addField("m_border_style", "Border (Mobile)", "text", "e.g. 1px solid #ccc", "mobile"); addField("hover_zoom", "Hover Zoom", "select", nullptr, "desktop", mkOpts({"none","in","out"}), -1, nullptr, "simple"); addSpacingBox("padding", "Padding", "desktop"); addSpacingBox("margin", "Margin", "desktop"); addSpacingBox("m_padding", "Mobile Padding", "mobile"); Loading Loading @@ -665,6 +676,14 @@ extern "C" { // Unique CSS class for responsive targeting std::string responsiveClass = std::string("we-") + getInstanceId().c_str(); bool hasHoverZoom = (hoverZoom == "in" || hoverZoom == "out"); if (hasHoverZoom) { // Shrink-wraps the <img> so overflow:hidden clips the // scaled image at its own visual edges instead of the // (otherwise block-level, full-width) wrapper's edges. wrapper.setAttribute("style", "overflow: hidden; display: inline-block; line-height: 0;"); } std::string renderSrc = src; const std::string &pfx = getPrefix(); if (!renderSrc.empty() && renderSrc[0] == '/' && !pfx.empty() && pfx != "/") { Loading Loading @@ -789,6 +808,22 @@ extern "C" { el.appendChild(styleTag); } if (hasHoverZoom) { // The scale transform has to live entirely in this <style> // block rather than the <img>'s own inline style attribute: // an inline style always wins over a class selector's rules // regardless of specificity, so a :hover rule could never // override an inline transform. libhtmlpp::HtmlElement zoomStyle("style"); std::string baseScale = (hoverZoom == "out") ? "1.15" : "1"; std::string hoverScale = (hoverZoom == "out") ? "1" : "1.1"; std::string zoomRule = "." + responsiveClass + " { transition: transform 0.3s ease; transform: scale(" + baseScale + "); } @media (min-width: 769px) { ." + responsiveClass + ":hover { transform: scale(" + hoverScale + "); } }"; zoomStyle.appendChild(libhtmlpp::TextElement(zoomRule.c_str())); el.appendChild(zoomStyle); } if (!customCss.empty()) { libhtmlpp::HtmlElement cssStyle("style"); cssStyle.appendChild(libhtmlpp::TextElement(customCss.c_str())); Loading editor/widgets/image/translations.h +1 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ namespace blogi { namespace webedit { namespace image_i18n { {"Height (Mobile)", "Höhe (Mobil)"}, {"Hidden", "Ausgeblendet"}, {"Hidden (Mobile)", "Ausgeblendet (Mobil)"}, {"Hover Zoom", "Hover-Zoom"}, {"Image Path (manual)", "Bildpfad (manuell)"}, {"Link URL", "Link-URL"}, {"Margin", "Außenabstand"}, Loading Loading
editor/html/js/editor.js +13 −3 Original line number Diff line number Diff line Loading @@ -2541,7 +2541,7 @@ // Fetches the project list; shared by initial load and the project manager dialog // (loadConnectionList) so projectsCache stays in sync after add/edit/delete. function loadProjectSelectOptions(callback) { EditorApi.listConnections().then(function(resp) { return EditorApi.listConnections().then(function(resp) { projectsCache = resp.connections || []; if (callback) callback(); }); Loading Loading @@ -2734,8 +2734,14 @@ status.className = 'set-status success'; var savedId = resp.id || conn.id; document.getElementById('conn-edit-id').value = savedId || ''; loadConnectionList(); // onProjectSelected() reads the newly saved project out of // projectsCache, so it must wait for loadConnectionList()'s // refetch to resolve -- calling it synchronously right after // loadConnectionList() ran against the still-stale cache and // blanked out the form the user just filled in/saved. loadConnectionList().then(function() { if (savedId) onProjectSelected(savedId); }); setTimeout(function() { status.textContent = ''; status.className = 'set-status'; }, 2000); }).catch(function(err) { status.textContent = I18n.t('I18N_SAVE_FAILED', 'Speichern fehlgeschlagen') + ': ' + (err.error || ''); Loading Loading @@ -2985,8 +2991,12 @@ } function loadConnectionList() { loadProjectSelectOptions(function() { return loadProjectSelectOptions(function() { renderConnectionList(projectsCache); // renderConnectionList() rebuilds #conn-list from scratch, so the // selection highlight and settings-form fields need reapplying // every time this runs, not just on the very first load. syncProjectManagerModalUI(); }); } Loading
editor/widgets/container/container.cpp +36 −0 Original line number Diff line number Diff line Loading @@ -124,6 +124,9 @@ extern "C" { std::string m_boxShadowColor = ""; std::string m_boxShadowCustom = ""; // Hover effect: lifts the container upward on hover (desktop only) bool hoverLift = false; // Visibility bool hidden = false; bool m_hidden = false; Loading Loading @@ -367,6 +370,11 @@ extern "C" { addField("box_shadow", "Box Shadow", "select", nullptr, "desktop", mkOpts({"none","sm","md","lg","card","float","inner"})); addField("box_shadow_color", "Box Shadow Color", "color_var", nullptr, "desktop"); addField("box_shadow_custom", "Box Shadow Custom", "text", "e.g. 0 4px 20px rgba(0,0,0,0.5)", "desktop"); addField("hover_lift", "Hover Lift", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *hlf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); json_object_object_add(hlf, "default", json_object_new_boolean(false)); } addField("hidden", "Hidden", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *hf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); Loading Loading @@ -507,6 +515,8 @@ extern "C" { element->SetAttribute("m_box_shadow_color", m_boxShadowColor.c_str()); element->SetAttribute("m_box_shadow_custom", m_boxShadowCustom.c_str()); element->SetAttribute("hover_lift", hoverLift ? "true" : "false"); // Visibility element->SetAttribute("hidden", hidden ? "true" : "false"); element->SetAttribute("m_hidden", m_hidden ? "true" : "false"); Loading Loading @@ -705,6 +715,9 @@ extern "C" { const char* loaded_m_bsh_custom = xml_data->Attribute("m_box_shadow_custom"); if (loaded_m_bsh_custom) m_boxShadowCustom = loaded_m_bsh_custom; const char* loaded_hover_lift = xml_data->Attribute("hover_lift"); if (loaded_hover_lift) hoverLift = (strcmp(loaded_hover_lift, "true") == 0); // Visibility const char* loaded_hidden = xml_data->Attribute("hidden"); if (loaded_hidden) hidden = (strcmp(loaded_hidden, "true") == 0); Loading Loading @@ -792,6 +805,7 @@ extern "C" { json_object_object_add(obj, "m_box_shadow", json_object_new_string(m_boxShadow.c_str())); json_object_object_add(obj, "m_box_shadow_color", json_object_new_string(m_boxShadowColor.c_str())); json_object_object_add(obj, "m_box_shadow_custom", json_object_new_string(m_boxShadowCustom.c_str())); json_object_object_add(obj, "hover_lift", json_object_new_boolean(hoverLift)); json_object_object_add(obj, "hidden", json_object_new_boolean(hidden)); json_object_object_add(obj, "m_hidden", json_object_new_boolean(m_hidden)); json_object_object_add(obj, "custom_css", json_object_new_string(customCss.c_str())); Loading Loading @@ -1067,6 +1081,14 @@ extern "C" { val = json_object_get_string(val_obj); if (val) m_boxShadowCustom = val; } if (json_object_object_get_ex(obj, "hover_lift", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); hoverLift = (s && (strcmp(s, "true") == 0 || strcmp(s, "1") == 0)); } else { hoverLift = json_object_get_boolean(val_obj); } } if (json_object_object_get_ex(obj, "hidden", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); Loading Loading @@ -1184,6 +1206,9 @@ extern "C" { if (hasShapeDivider) { style += " position: relative;"; } if (hoverLift) { style += " transition: transform 0.25s ease, box-shadow 0.25s ease;"; } if (hidden) { style += " display: none;"; } Loading Loading @@ -1355,6 +1380,17 @@ extern "C" { el.appendChild(mobileLightStyle); } // Hover lift: nudges the container upward with a soft shadow on // hover. Desktop-only (matches the mobile breakpoint used above) // since :hover has no reliable touch equivalent. if (hoverLift) { libhtmlpp::HtmlElement hoverStyle("style"); std::string hoverRule = "@media (min-width: 769px) { ." + responsiveClass + ":hover { transform: translateY(-8px); box-shadow: 0 20px 45px rgba(0,0,0,0.15); } }"; hoverStyle.appendChild(libhtmlpp::TextElement(hoverRule.c_str())); el.appendChild(hoverStyle); } if (!customCss.empty()) { libhtmlpp::HtmlElement cssStyle("style"); // Emit raw — user writes full selectors, e.g. .we-xxx { color: red; } Loading
editor/widgets/container/translations.h +1 −0 Original line number Diff line number Diff line Loading @@ -54,6 +54,7 @@ namespace blogi { namespace webedit { namespace container_i18n { {"Gradient Custom", "Eigener Farbverlauf"}, {"Height", "Höhe"}, {"Hidden", "Ausgeblendet"}, {"Hover Lift", "Hover-Anhebung"}, {"Margin", "Außenabstand"}, {"Mobile Background Color", "Hintergrundfarbe (Mobil)"}, {"Mobile Background Color (Light)", "Hintergrundfarbe (Mobil Hell)"}, Loading
editor/widgets/image/image.cpp +35 −0 Original line number Diff line number Diff line Loading @@ -83,6 +83,10 @@ extern "C" { // Fullscreen bool fullscreen = false; // Hover zoom: "in" grows the image on hover, "out" starts the // image zoomed in and settles back to normal size on hover std::string hoverZoom = "none"; // Custom CSS std::string customCss = ""; Loading Loading @@ -284,6 +288,7 @@ extern "C" { element->SetAttribute("object_fit", objectFit.c_str()); element->SetAttribute("border_radius", borderRadius.c_str()); element->SetAttribute("border_style", borderStyle.c_str()); element->SetAttribute("hover_zoom", hoverZoom.c_str()); element->SetAttribute("link_url", linkUrl.c_str()); element->SetAttribute("media_id", mediaId.c_str()); element->SetAttribute("media_ext", mediaExt.c_str()); Loading Loading @@ -339,6 +344,7 @@ extern "C" { val = xml_data->Attribute("object_fit"); if (val) objectFit = val; val = xml_data->Attribute("border_radius"); if (val) borderRadius = val; val = xml_data->Attribute("border_style"); if (val) borderStyle = val; val = xml_data->Attribute("hover_zoom"); if (val) hoverZoom = val; val = xml_data->Attribute("link_url"); if (val) linkUrl = val; val = xml_data->Attribute("media_id"); if (val) mediaId = val; val = xml_data->Attribute("media_ext"); if (val) mediaExt = val; Loading Loading @@ -398,6 +404,7 @@ extern "C" { json_object_object_add(obj, "object_fit", json_object_new_string(objectFit.c_str())); json_object_object_add(obj, "border_radius", json_object_new_string(borderRadius.c_str())); json_object_object_add(obj, "border_style", json_object_new_string(borderStyle.c_str())); json_object_object_add(obj, "hover_zoom", json_object_new_string(hoverZoom.c_str())); json_object_object_add(obj, "link_url", json_object_new_string(linkUrl.c_str())); json_object_object_add(obj, "media_id", json_object_new_string(mediaId.c_str())); json_object_object_add(obj, "media_ext", json_object_new_string(mediaExt.c_str())); Loading Loading @@ -459,6 +466,9 @@ extern "C" { if (json_object_object_get_ex(obj, "border_style", &val_obj)) { val = json_object_get_string(val_obj); if (val) borderStyle = val; } if (json_object_object_get_ex(obj, "hover_zoom", &val_obj)) { val = json_object_get_string(val_obj); if (val) hoverZoom = val; } if (json_object_object_get_ex(obj, "link_url", &val_obj)) { val = json_object_get_string(val_obj); if (val) linkUrl = val; } Loading Loading @@ -623,6 +633,7 @@ extern "C" { addField("border_radius", "Border Radius", "text", "0"); addField("border_style", "Border", "text", "e.g. 1px solid #ccc", "desktop"); addField("m_border_style", "Border (Mobile)", "text", "e.g. 1px solid #ccc", "mobile"); addField("hover_zoom", "Hover Zoom", "select", nullptr, "desktop", mkOpts({"none","in","out"}), -1, nullptr, "simple"); addSpacingBox("padding", "Padding", "desktop"); addSpacingBox("margin", "Margin", "desktop"); addSpacingBox("m_padding", "Mobile Padding", "mobile"); Loading Loading @@ -665,6 +676,14 @@ extern "C" { // Unique CSS class for responsive targeting std::string responsiveClass = std::string("we-") + getInstanceId().c_str(); bool hasHoverZoom = (hoverZoom == "in" || hoverZoom == "out"); if (hasHoverZoom) { // Shrink-wraps the <img> so overflow:hidden clips the // scaled image at its own visual edges instead of the // (otherwise block-level, full-width) wrapper's edges. wrapper.setAttribute("style", "overflow: hidden; display: inline-block; line-height: 0;"); } std::string renderSrc = src; const std::string &pfx = getPrefix(); if (!renderSrc.empty() && renderSrc[0] == '/' && !pfx.empty() && pfx != "/") { Loading Loading @@ -789,6 +808,22 @@ extern "C" { el.appendChild(styleTag); } if (hasHoverZoom) { // The scale transform has to live entirely in this <style> // block rather than the <img>'s own inline style attribute: // an inline style always wins over a class selector's rules // regardless of specificity, so a :hover rule could never // override an inline transform. libhtmlpp::HtmlElement zoomStyle("style"); std::string baseScale = (hoverZoom == "out") ? "1.15" : "1"; std::string hoverScale = (hoverZoom == "out") ? "1" : "1.1"; std::string zoomRule = "." + responsiveClass + " { transition: transform 0.3s ease; transform: scale(" + baseScale + "); } @media (min-width: 769px) { ." + responsiveClass + ":hover { transform: scale(" + hoverScale + "); } }"; zoomStyle.appendChild(libhtmlpp::TextElement(zoomRule.c_str())); el.appendChild(zoomStyle); } if (!customCss.empty()) { libhtmlpp::HtmlElement cssStyle("style"); cssStyle.appendChild(libhtmlpp::TextElement(customCss.c_str())); Loading
editor/widgets/image/translations.h +1 −0 Original line number Diff line number Diff line Loading @@ -45,6 +45,7 @@ namespace blogi { namespace webedit { namespace image_i18n { {"Height (Mobile)", "Höhe (Mobil)"}, {"Hidden", "Ausgeblendet"}, {"Hidden (Mobile)", "Ausgeblendet (Mobil)"}, {"Hover Zoom", "Hover-Zoom"}, {"Image Path (manual)", "Bildpfad (manuell)"}, {"Link URL", "Link-URL"}, {"Margin", "Außenabstand"}, Loading