Loading editor/widgets/container/container.cpp +112 −0 Changes for editor/widgets/container/container.cpp: 112 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -102,6 +102,15 @@ extern "C" { std::string verticalAlign = ""; std::string m_verticalAlign = ""; // Shape divider: a decorative SVG (wave/oval) cut into the // container's top or bottom edge, e.g. to end a hero background // without a hard horizontal line. std::string shapeDividerType = "none"; // none, wave, oval std::string shapeDividerPosition = "bottom"; // top, bottom std::string shapeDividerColor = ""; std::string shapeDividerHeight = "80px"; bool shapeDividerFlip = false; // Box shadow std::string boxShadow = "none"; std::string boxShadowColor = ""; Loading Loading @@ -331,6 +340,15 @@ extern "C" { addSpacingBox("padding", "Padding", "desktop"); addSpacingBox("margin", "Margin", "desktop"); addField("vertical_align", "Vertical Align", "select", nullptr, "desktop", mkOpts({"top","center","bottom"})); addField("shape_divider_type", "Shape Divider", "select", nullptr, "desktop", mkOpts({"none","wave","oval"}), "simple"); addField("shape_divider_position", "Shape Divider Position", "select", nullptr, "desktop", mkOpts({"bottom","top"}), "simple"); addField("shape_divider_color", "Shape Divider Color", "color_var", nullptr, "desktop", nullptr, "simple"); addField("shape_divider_height", "Shape Divider Height", "text", "80px", "desktop"); addField("shape_divider_flip", "Shape Divider Flip", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *sdf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); json_object_object_add(sdf, "default", json_object_new_boolean(false)); } 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"); Loading Loading @@ -453,6 +471,14 @@ extern "C" { // Vertical alignment element->SetAttribute("vertical_align", verticalAlign.c_str()); element->SetAttribute("m_vertical_align", m_verticalAlign.c_str()); // Shape divider element->SetAttribute("shape_divider_type", shapeDividerType.c_str()); element->SetAttribute("shape_divider_position", shapeDividerPosition.c_str()); element->SetAttribute("shape_divider_color", shapeDividerColor.c_str()); element->SetAttribute("shape_divider_height", shapeDividerHeight.c_str()); element->SetAttribute("shape_divider_flip", shapeDividerFlip ? "true" : "false"); element->SetAttribute("box_shadow", boxShadow.c_str()); element->SetAttribute("box_shadow_color", boxShadowColor.c_str()); element->SetAttribute("box_shadow_custom", boxShadowCustom.c_str()); Loading Loading @@ -622,6 +648,19 @@ extern "C" { if (loaded_va) verticalAlign = loaded_va; const char* loaded_m_va = xml_data->Attribute("m_vertical_align"); if (loaded_m_va) m_verticalAlign = loaded_m_va; // Shape divider const char* loaded_sdt = xml_data->Attribute("shape_divider_type"); if (loaded_sdt) shapeDividerType = loaded_sdt; const char* loaded_sdp = xml_data->Attribute("shape_divider_position"); if (loaded_sdp) shapeDividerPosition = loaded_sdp; const char* loaded_sdc = xml_data->Attribute("shape_divider_color"); if (loaded_sdc) shapeDividerColor = loaded_sdc; const char* loaded_sdh = xml_data->Attribute("shape_divider_height"); if (loaded_sdh) shapeDividerHeight = loaded_sdh; const char* loaded_sdf = xml_data->Attribute("shape_divider_flip"); if (loaded_sdf) shapeDividerFlip = (strcmp(loaded_sdf, "true") == 0); const char* loaded_bsh = xml_data->Attribute("box_shadow"); if (loaded_bsh) boxShadow = loaded_bsh; const char* loaded_bsh_color = xml_data->Attribute("box_shadow_color"); Loading Loading @@ -705,6 +744,11 @@ extern "C" { json_object_object_add(obj, "m_height", json_object_new_string(m_height.c_str())); json_object_object_add(obj, "vertical_align", json_object_new_string(verticalAlign.c_str())); json_object_object_add(obj, "m_vertical_align", json_object_new_string(m_verticalAlign.c_str())); json_object_object_add(obj, "shape_divider_type", json_object_new_string(shapeDividerType.c_str())); json_object_object_add(obj, "shape_divider_position", json_object_new_string(shapeDividerPosition.c_str())); json_object_object_add(obj, "shape_divider_color", json_object_new_string(shapeDividerColor.c_str())); json_object_object_add(obj, "shape_divider_height", json_object_new_string(shapeDividerHeight.c_str())); json_object_object_add(obj, "shape_divider_flip", json_object_new_boolean(shapeDividerFlip)); json_object_object_add(obj, "box_shadow", json_object_new_string(boxShadow.c_str())); json_object_object_add(obj, "box_shadow_color", json_object_new_string(boxShadowColor.c_str())); json_object_object_add(obj, "box_shadow_custom", json_object_new_string(boxShadowCustom.c_str())); Loading Loading @@ -926,6 +970,30 @@ extern "C" { val = json_object_get_string(val_obj); if (val) m_verticalAlign = val; } if (json_object_object_get_ex(obj, "shape_divider_type", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerType = val; } if (json_object_object_get_ex(obj, "shape_divider_position", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerPosition = val; } if (json_object_object_get_ex(obj, "shape_divider_color", &val_obj)) { val = json_object_get_string(val_obj); if (val) shapeDividerColor = val; } if (json_object_object_get_ex(obj, "shape_divider_height", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerHeight = val; } if (json_object_object_get_ex(obj, "shape_divider_flip", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); shapeDividerFlip = (s && (strcmp(s, "true") == 0 || strcmp(s, "1") == 0)); } else { shapeDividerFlip = json_object_get_boolean(val_obj); } } if (json_object_object_get_ex(obj, "box_shadow", &val_obj)) { val = json_object_get_string(val_obj); if (val) boxShadow = val; Loading Loading @@ -1061,6 +1129,10 @@ extern "C" { std::string css = shadowCss(boxShadow, boxShadowColor); if (!css.empty()) style += " box-shadow: " + css + ";"; } bool hasShapeDivider = (shapeDividerType == "wave" || shapeDividerType == "oval"); if (hasShapeDivider) { style += " position: relative;"; } if (hidden) { style += " display: none;"; } Loading @@ -1072,6 +1144,46 @@ extern "C" { } cnt.setAttribute("class", cls.c_str()); // Shape divider: a decorative wave/oval SVG pinned to the // container's top or bottom edge (see member field comment). if (hasShapeDivider) { std::string path; if (shapeDividerType == "wave") { path = "M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43," "512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34" "C989.49,25,1113,44.11,1200,89.66V0Z"; } else { // oval path = "M0,0Q600,100,1200,0V120H0Z"; } std::string dividerStyle = "position: absolute; left: 0; width: 100%; overflow: hidden;" " line-height: 0; pointer-events: none;"; dividerStyle += (shapeDividerPosition == "top") ? " top: 0;" : " bottom: 0;"; if (!shapeDividerHeight.empty()) dividerStyle += " height: " + shapeDividerHeight + ";"; std::string transform; if (shapeDividerPosition == "top") transform += "rotate(180deg) "; if (shapeDividerFlip) transform += "scaleX(-1) "; if (!transform.empty()) dividerStyle += " transform: " + transform + ";"; libhtmlpp::HtmlElement dividerWrap("div"); dividerWrap.setAttribute("class", (responsiveClass + "-shape-divider").c_str()); dividerWrap.setAttribute("style", dividerStyle.c_str()); libhtmlpp::HtmlElement svg("svg"); svg.setAttribute("viewBox", "0 0 1200 120"); svg.setAttribute("preserveAspectRatio", "none"); svg.setAttribute("style", "display: block; width: 100%; height: 100%;"); libhtmlpp::HtmlElement svgPath("path"); svgPath.setAttribute("d", path.c_str()); std::string fill = shapeDividerColor.empty() ? "var(--blogi-bg)" : shapeDividerColor; svgPath.setAttribute("fill", fill.c_str()); svg.appendChild(svgPath); dividerWrap.appendChild(svg); cnt.appendChild(dividerWrap); } // Mobile responsive overrides via media query std::string mobileCSS; if (!m_backgroundColor.empty() && m_backgroundColor != "none" && m_backgroundColor != "transparent") { Loading editor/widgets/container/translations.h +5 −0 Changes for editor/widgets/container/translations.h: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -79,6 +79,11 @@ namespace blogi { namespace webedit { namespace container_i18n { {"Mobile Width", "Breite (Mobil)"}, {"Opacity", "Deckkraft"}, {"Padding", "Innenabstand"}, {"Shape Divider", "Formteiler"}, {"Shape Divider Position", "Formteiler-Position"}, {"Shape Divider Color", "Formteiler-Farbe"}, {"Shape Divider Height", "Formteiler-Höhe"}, {"Shape Divider Flip", "Formteiler spiegeln"}, {"Vertical Align", "Vertikale Ausrichtung"}, {"Width", "Breite"}, }; Loading editor/widgets/section/section.cpp +110 −0 Changes for editor/widgets/section/section.cpp: 110 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -99,6 +99,15 @@ extern "C" { std::string verticalAlign = ""; std::string m_verticalAlign = ""; // Shape divider: a decorative SVG (wave/oval) cut into the // section's top or bottom edge, e.g. to end a hero section // without a hard horizontal line. std::string shapeDividerType = "none"; // none, wave, oval std::string shapeDividerPosition = "bottom"; // top, bottom std::string shapeDividerColor = ""; std::string shapeDividerHeight = "80px"; bool shapeDividerFlip = false; // Visibility bool hidden = false; bool m_hidden = false; Loading Loading @@ -241,6 +250,15 @@ extern "C" { addField("border_style", "Border", "text", "e.g. 1px solid #ccc", "desktop"); addField("opacity", "Opacity", "text", "0.0 - 1.0", "desktop"); addField("vertical_align", "Vertical Align", "select", nullptr, "desktop", mkOpts({"top","center","bottom"})); addField("shape_divider_type", "Shape Divider", "select", nullptr, "desktop", mkOpts({"none","wave","oval"}), "simple"); addField("shape_divider_position", "Shape Divider Position", "select", nullptr, "desktop", mkOpts({"bottom","top"}), "simple"); addField("shape_divider_color", "Shape Divider Color", "color_var", nullptr, "desktop", nullptr, "simple"); addField("shape_divider_height", "Shape Divider Height", "text", "80px", "desktop"); addField("shape_divider_flip", "Shape Divider Flip", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *sdf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); json_object_object_add(sdf, "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 @@ -351,6 +369,13 @@ extern "C" { element->SetAttribute("vertical_align", verticalAlign.c_str()); element->SetAttribute("m_vertical_align", m_verticalAlign.c_str()); // Shape divider element->SetAttribute("shape_divider_type", shapeDividerType.c_str()); element->SetAttribute("shape_divider_position", shapeDividerPosition.c_str()); element->SetAttribute("shape_divider_color", shapeDividerColor.c_str()); element->SetAttribute("shape_divider_height", shapeDividerHeight.c_str()); element->SetAttribute("shape_divider_flip", shapeDividerFlip ? "true" : "false"); // Visibility element->SetAttribute("hidden", hidden ? "true" : "false"); element->SetAttribute("m_hidden", m_hidden ? "true" : "false"); Loading Loading @@ -504,6 +529,18 @@ extern "C" { val = xml_data->Attribute("m_vertical_align"); if (val) m_verticalAlign = val; // Shape divider val = xml_data->Attribute("shape_divider_type"); if (val) shapeDividerType = val; val = xml_data->Attribute("shape_divider_position"); if (val) shapeDividerPosition = val; val = xml_data->Attribute("shape_divider_color"); if (val) shapeDividerColor = val; val = xml_data->Attribute("shape_divider_height"); if (val) shapeDividerHeight = val; val = xml_data->Attribute("shape_divider_flip"); if (val) shapeDividerFlip = (strcmp(val, "true") == 0); // Visibility const char* loaded_hidden = xml_data->Attribute("hidden"); if (loaded_hidden) hidden = (strcmp(loaded_hidden, "true") == 0); Loading Loading @@ -568,6 +605,11 @@ extern "C" { json_object_object_add(obj, "m_height", json_object_new_string(m_height.c_str())); json_object_object_add(obj, "vertical_align", json_object_new_string(verticalAlign.c_str())); json_object_object_add(obj, "m_vertical_align", json_object_new_string(m_verticalAlign.c_str())); json_object_object_add(obj, "shape_divider_type", json_object_new_string(shapeDividerType.c_str())); json_object_object_add(obj, "shape_divider_position", json_object_new_string(shapeDividerPosition.c_str())); json_object_object_add(obj, "shape_divider_color", json_object_new_string(shapeDividerColor.c_str())); json_object_object_add(obj, "shape_divider_height", json_object_new_string(shapeDividerHeight.c_str())); json_object_object_add(obj, "shape_divider_flip", json_object_new_boolean(shapeDividerFlip)); 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 @@ -767,6 +809,30 @@ extern "C" { val = json_object_get_string(val_obj); if (val) m_verticalAlign = val; } if (json_object_object_get_ex(obj, "shape_divider_type", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerType = val; } if (json_object_object_get_ex(obj, "shape_divider_position", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerPosition = val; } if (json_object_object_get_ex(obj, "shape_divider_color", &val_obj)) { val = json_object_get_string(val_obj); if (val) shapeDividerColor = val; } if (json_object_object_get_ex(obj, "shape_divider_height", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerHeight = val; } if (json_object_object_get_ex(obj, "shape_divider_flip", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); shapeDividerFlip = (s && (strcmp(s, "true") == 0 || strcmp(s, "1") == 0)); } else { shapeDividerFlip = 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 @@ -857,6 +923,10 @@ extern "C" { else if (verticalAlign == "center") style += " justify-content: center;"; else if (verticalAlign == "bottom") style += " justify-content: flex-end;"; } bool hasShapeDivider = (shapeDividerType == "wave" || shapeDividerType == "oval"); if (hasShapeDivider) { style += " position: relative;"; } if (hidden) { style += " display: none;"; } Loading @@ -868,6 +938,46 @@ extern "C" { } sect.setAttribute("class", cls.c_str()); // Shape divider: a decorative wave/oval SVG pinned to the // section's top or bottom edge (see member field comment). if (hasShapeDivider) { std::string path; if (shapeDividerType == "wave") { path = "M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43," "512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34" "C989.49,25,1113,44.11,1200,89.66V0Z"; } else { // oval path = "M0,0Q600,100,1200,0V120H0Z"; } std::string dividerStyle = "position: absolute; left: 0; width: 100%; overflow: hidden;" " line-height: 0; pointer-events: none;"; dividerStyle += (shapeDividerPosition == "top") ? " top: 0;" : " bottom: 0;"; if (!shapeDividerHeight.empty()) dividerStyle += " height: " + shapeDividerHeight + ";"; std::string transform; if (shapeDividerPosition == "top") transform += "rotate(180deg) "; if (shapeDividerFlip) transform += "scaleX(-1) "; if (!transform.empty()) dividerStyle += " transform: " + transform + ";"; libhtmlpp::HtmlElement dividerWrap("div"); dividerWrap.setAttribute("class", (responsiveClass + "-shape-divider").c_str()); dividerWrap.setAttribute("style", dividerStyle.c_str()); libhtmlpp::HtmlElement svg("svg"); svg.setAttribute("viewBox", "0 0 1200 120"); svg.setAttribute("preserveAspectRatio", "none"); svg.setAttribute("style", "display: block; width: 100%; height: 100%;"); libhtmlpp::HtmlElement svgPath("path"); svgPath.setAttribute("d", path.c_str()); std::string fill = shapeDividerColor.empty() ? "var(--blogi-bg)" : shapeDividerColor; svgPath.setAttribute("fill", fill.c_str()); svg.appendChild(svgPath); dividerWrap.appendChild(svg); sect.appendChild(dividerWrap); } // Mobile responsive overrides via media query std::string mobileCSS; if (!m_backgroundColor.empty() && m_backgroundColor != "none" && m_backgroundColor != "transparent") { Loading editor/widgets/section/translations.h +5 −0 Changes for editor/widgets/section/translations.h: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -70,6 +70,11 @@ namespace blogi { namespace webedit { namespace section_i18n { {"Mobile Width", "Breite (Mobil)"}, {"Opacity", "Deckkraft"}, {"Padding", "Innenabstand"}, {"Shape Divider", "Formteiler"}, {"Shape Divider Position", "Formteiler-Position"}, {"Shape Divider Color", "Formteiler-Farbe"}, {"Shape Divider Height", "Formteiler-Höhe"}, {"Shape Divider Flip", "Formteiler spiegeln"}, {"Vertical Align", "Vertikale Ausrichtung"}, {"Width", "Breite"}, }; Loading Loading
editor/widgets/container/container.cpp +112 −0 Changes for editor/widgets/container/container.cpp: 112 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -102,6 +102,15 @@ extern "C" { std::string verticalAlign = ""; std::string m_verticalAlign = ""; // Shape divider: a decorative SVG (wave/oval) cut into the // container's top or bottom edge, e.g. to end a hero background // without a hard horizontal line. std::string shapeDividerType = "none"; // none, wave, oval std::string shapeDividerPosition = "bottom"; // top, bottom std::string shapeDividerColor = ""; std::string shapeDividerHeight = "80px"; bool shapeDividerFlip = false; // Box shadow std::string boxShadow = "none"; std::string boxShadowColor = ""; Loading Loading @@ -331,6 +340,15 @@ extern "C" { addSpacingBox("padding", "Padding", "desktop"); addSpacingBox("margin", "Margin", "desktop"); addField("vertical_align", "Vertical Align", "select", nullptr, "desktop", mkOpts({"top","center","bottom"})); addField("shape_divider_type", "Shape Divider", "select", nullptr, "desktop", mkOpts({"none","wave","oval"}), "simple"); addField("shape_divider_position", "Shape Divider Position", "select", nullptr, "desktop", mkOpts({"bottom","top"}), "simple"); addField("shape_divider_color", "Shape Divider Color", "color_var", nullptr, "desktop", nullptr, "simple"); addField("shape_divider_height", "Shape Divider Height", "text", "80px", "desktop"); addField("shape_divider_flip", "Shape Divider Flip", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *sdf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); json_object_object_add(sdf, "default", json_object_new_boolean(false)); } 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"); Loading Loading @@ -453,6 +471,14 @@ extern "C" { // Vertical alignment element->SetAttribute("vertical_align", verticalAlign.c_str()); element->SetAttribute("m_vertical_align", m_verticalAlign.c_str()); // Shape divider element->SetAttribute("shape_divider_type", shapeDividerType.c_str()); element->SetAttribute("shape_divider_position", shapeDividerPosition.c_str()); element->SetAttribute("shape_divider_color", shapeDividerColor.c_str()); element->SetAttribute("shape_divider_height", shapeDividerHeight.c_str()); element->SetAttribute("shape_divider_flip", shapeDividerFlip ? "true" : "false"); element->SetAttribute("box_shadow", boxShadow.c_str()); element->SetAttribute("box_shadow_color", boxShadowColor.c_str()); element->SetAttribute("box_shadow_custom", boxShadowCustom.c_str()); Loading Loading @@ -622,6 +648,19 @@ extern "C" { if (loaded_va) verticalAlign = loaded_va; const char* loaded_m_va = xml_data->Attribute("m_vertical_align"); if (loaded_m_va) m_verticalAlign = loaded_m_va; // Shape divider const char* loaded_sdt = xml_data->Attribute("shape_divider_type"); if (loaded_sdt) shapeDividerType = loaded_sdt; const char* loaded_sdp = xml_data->Attribute("shape_divider_position"); if (loaded_sdp) shapeDividerPosition = loaded_sdp; const char* loaded_sdc = xml_data->Attribute("shape_divider_color"); if (loaded_sdc) shapeDividerColor = loaded_sdc; const char* loaded_sdh = xml_data->Attribute("shape_divider_height"); if (loaded_sdh) shapeDividerHeight = loaded_sdh; const char* loaded_sdf = xml_data->Attribute("shape_divider_flip"); if (loaded_sdf) shapeDividerFlip = (strcmp(loaded_sdf, "true") == 0); const char* loaded_bsh = xml_data->Attribute("box_shadow"); if (loaded_bsh) boxShadow = loaded_bsh; const char* loaded_bsh_color = xml_data->Attribute("box_shadow_color"); Loading Loading @@ -705,6 +744,11 @@ extern "C" { json_object_object_add(obj, "m_height", json_object_new_string(m_height.c_str())); json_object_object_add(obj, "vertical_align", json_object_new_string(verticalAlign.c_str())); json_object_object_add(obj, "m_vertical_align", json_object_new_string(m_verticalAlign.c_str())); json_object_object_add(obj, "shape_divider_type", json_object_new_string(shapeDividerType.c_str())); json_object_object_add(obj, "shape_divider_position", json_object_new_string(shapeDividerPosition.c_str())); json_object_object_add(obj, "shape_divider_color", json_object_new_string(shapeDividerColor.c_str())); json_object_object_add(obj, "shape_divider_height", json_object_new_string(shapeDividerHeight.c_str())); json_object_object_add(obj, "shape_divider_flip", json_object_new_boolean(shapeDividerFlip)); json_object_object_add(obj, "box_shadow", json_object_new_string(boxShadow.c_str())); json_object_object_add(obj, "box_shadow_color", json_object_new_string(boxShadowColor.c_str())); json_object_object_add(obj, "box_shadow_custom", json_object_new_string(boxShadowCustom.c_str())); Loading Loading @@ -926,6 +970,30 @@ extern "C" { val = json_object_get_string(val_obj); if (val) m_verticalAlign = val; } if (json_object_object_get_ex(obj, "shape_divider_type", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerType = val; } if (json_object_object_get_ex(obj, "shape_divider_position", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerPosition = val; } if (json_object_object_get_ex(obj, "shape_divider_color", &val_obj)) { val = json_object_get_string(val_obj); if (val) shapeDividerColor = val; } if (json_object_object_get_ex(obj, "shape_divider_height", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerHeight = val; } if (json_object_object_get_ex(obj, "shape_divider_flip", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); shapeDividerFlip = (s && (strcmp(s, "true") == 0 || strcmp(s, "1") == 0)); } else { shapeDividerFlip = json_object_get_boolean(val_obj); } } if (json_object_object_get_ex(obj, "box_shadow", &val_obj)) { val = json_object_get_string(val_obj); if (val) boxShadow = val; Loading Loading @@ -1061,6 +1129,10 @@ extern "C" { std::string css = shadowCss(boxShadow, boxShadowColor); if (!css.empty()) style += " box-shadow: " + css + ";"; } bool hasShapeDivider = (shapeDividerType == "wave" || shapeDividerType == "oval"); if (hasShapeDivider) { style += " position: relative;"; } if (hidden) { style += " display: none;"; } Loading @@ -1072,6 +1144,46 @@ extern "C" { } cnt.setAttribute("class", cls.c_str()); // Shape divider: a decorative wave/oval SVG pinned to the // container's top or bottom edge (see member field comment). if (hasShapeDivider) { std::string path; if (shapeDividerType == "wave") { path = "M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43," "512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34" "C989.49,25,1113,44.11,1200,89.66V0Z"; } else { // oval path = "M0,0Q600,100,1200,0V120H0Z"; } std::string dividerStyle = "position: absolute; left: 0; width: 100%; overflow: hidden;" " line-height: 0; pointer-events: none;"; dividerStyle += (shapeDividerPosition == "top") ? " top: 0;" : " bottom: 0;"; if (!shapeDividerHeight.empty()) dividerStyle += " height: " + shapeDividerHeight + ";"; std::string transform; if (shapeDividerPosition == "top") transform += "rotate(180deg) "; if (shapeDividerFlip) transform += "scaleX(-1) "; if (!transform.empty()) dividerStyle += " transform: " + transform + ";"; libhtmlpp::HtmlElement dividerWrap("div"); dividerWrap.setAttribute("class", (responsiveClass + "-shape-divider").c_str()); dividerWrap.setAttribute("style", dividerStyle.c_str()); libhtmlpp::HtmlElement svg("svg"); svg.setAttribute("viewBox", "0 0 1200 120"); svg.setAttribute("preserveAspectRatio", "none"); svg.setAttribute("style", "display: block; width: 100%; height: 100%;"); libhtmlpp::HtmlElement svgPath("path"); svgPath.setAttribute("d", path.c_str()); std::string fill = shapeDividerColor.empty() ? "var(--blogi-bg)" : shapeDividerColor; svgPath.setAttribute("fill", fill.c_str()); svg.appendChild(svgPath); dividerWrap.appendChild(svg); cnt.appendChild(dividerWrap); } // Mobile responsive overrides via media query std::string mobileCSS; if (!m_backgroundColor.empty() && m_backgroundColor != "none" && m_backgroundColor != "transparent") { Loading
editor/widgets/container/translations.h +5 −0 Changes for editor/widgets/container/translations.h: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -79,6 +79,11 @@ namespace blogi { namespace webedit { namespace container_i18n { {"Mobile Width", "Breite (Mobil)"}, {"Opacity", "Deckkraft"}, {"Padding", "Innenabstand"}, {"Shape Divider", "Formteiler"}, {"Shape Divider Position", "Formteiler-Position"}, {"Shape Divider Color", "Formteiler-Farbe"}, {"Shape Divider Height", "Formteiler-Höhe"}, {"Shape Divider Flip", "Formteiler spiegeln"}, {"Vertical Align", "Vertikale Ausrichtung"}, {"Width", "Breite"}, }; Loading
editor/widgets/section/section.cpp +110 −0 Changes for editor/widgets/section/section.cpp: 110 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -99,6 +99,15 @@ extern "C" { std::string verticalAlign = ""; std::string m_verticalAlign = ""; // Shape divider: a decorative SVG (wave/oval) cut into the // section's top or bottom edge, e.g. to end a hero section // without a hard horizontal line. std::string shapeDividerType = "none"; // none, wave, oval std::string shapeDividerPosition = "bottom"; // top, bottom std::string shapeDividerColor = ""; std::string shapeDividerHeight = "80px"; bool shapeDividerFlip = false; // Visibility bool hidden = false; bool m_hidden = false; Loading Loading @@ -241,6 +250,15 @@ extern "C" { addField("border_style", "Border", "text", "e.g. 1px solid #ccc", "desktop"); addField("opacity", "Opacity", "text", "0.0 - 1.0", "desktop"); addField("vertical_align", "Vertical Align", "select", nullptr, "desktop", mkOpts({"top","center","bottom"})); addField("shape_divider_type", "Shape Divider", "select", nullptr, "desktop", mkOpts({"none","wave","oval"}), "simple"); addField("shape_divider_position", "Shape Divider Position", "select", nullptr, "desktop", mkOpts({"bottom","top"}), "simple"); addField("shape_divider_color", "Shape Divider Color", "color_var", nullptr, "desktop", nullptr, "simple"); addField("shape_divider_height", "Shape Divider Height", "text", "80px", "desktop"); addField("shape_divider_flip", "Shape Divider Flip", "checkbox", nullptr, "desktop", nullptr, "simple"); { json_object *sdf = json_object_array_get_idx(arr, json_object_array_length(arr) - 1); json_object_object_add(sdf, "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 @@ -351,6 +369,13 @@ extern "C" { element->SetAttribute("vertical_align", verticalAlign.c_str()); element->SetAttribute("m_vertical_align", m_verticalAlign.c_str()); // Shape divider element->SetAttribute("shape_divider_type", shapeDividerType.c_str()); element->SetAttribute("shape_divider_position", shapeDividerPosition.c_str()); element->SetAttribute("shape_divider_color", shapeDividerColor.c_str()); element->SetAttribute("shape_divider_height", shapeDividerHeight.c_str()); element->SetAttribute("shape_divider_flip", shapeDividerFlip ? "true" : "false"); // Visibility element->SetAttribute("hidden", hidden ? "true" : "false"); element->SetAttribute("m_hidden", m_hidden ? "true" : "false"); Loading Loading @@ -504,6 +529,18 @@ extern "C" { val = xml_data->Attribute("m_vertical_align"); if (val) m_verticalAlign = val; // Shape divider val = xml_data->Attribute("shape_divider_type"); if (val) shapeDividerType = val; val = xml_data->Attribute("shape_divider_position"); if (val) shapeDividerPosition = val; val = xml_data->Attribute("shape_divider_color"); if (val) shapeDividerColor = val; val = xml_data->Attribute("shape_divider_height"); if (val) shapeDividerHeight = val; val = xml_data->Attribute("shape_divider_flip"); if (val) shapeDividerFlip = (strcmp(val, "true") == 0); // Visibility const char* loaded_hidden = xml_data->Attribute("hidden"); if (loaded_hidden) hidden = (strcmp(loaded_hidden, "true") == 0); Loading Loading @@ -568,6 +605,11 @@ extern "C" { json_object_object_add(obj, "m_height", json_object_new_string(m_height.c_str())); json_object_object_add(obj, "vertical_align", json_object_new_string(verticalAlign.c_str())); json_object_object_add(obj, "m_vertical_align", json_object_new_string(m_verticalAlign.c_str())); json_object_object_add(obj, "shape_divider_type", json_object_new_string(shapeDividerType.c_str())); json_object_object_add(obj, "shape_divider_position", json_object_new_string(shapeDividerPosition.c_str())); json_object_object_add(obj, "shape_divider_color", json_object_new_string(shapeDividerColor.c_str())); json_object_object_add(obj, "shape_divider_height", json_object_new_string(shapeDividerHeight.c_str())); json_object_object_add(obj, "shape_divider_flip", json_object_new_boolean(shapeDividerFlip)); 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 @@ -767,6 +809,30 @@ extern "C" { val = json_object_get_string(val_obj); if (val) m_verticalAlign = val; } if (json_object_object_get_ex(obj, "shape_divider_type", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerType = val; } if (json_object_object_get_ex(obj, "shape_divider_position", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerPosition = val; } if (json_object_object_get_ex(obj, "shape_divider_color", &val_obj)) { val = json_object_get_string(val_obj); if (val) shapeDividerColor = val; } if (json_object_object_get_ex(obj, "shape_divider_height", &val_obj)) { val = json_object_get_string(val_obj); if (val && val[0]) shapeDividerHeight = val; } if (json_object_object_get_ex(obj, "shape_divider_flip", &val_obj)) { if (json_object_is_type(val_obj, json_type_string)) { const char *s = json_object_get_string(val_obj); shapeDividerFlip = (s && (strcmp(s, "true") == 0 || strcmp(s, "1") == 0)); } else { shapeDividerFlip = 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 @@ -857,6 +923,10 @@ extern "C" { else if (verticalAlign == "center") style += " justify-content: center;"; else if (verticalAlign == "bottom") style += " justify-content: flex-end;"; } bool hasShapeDivider = (shapeDividerType == "wave" || shapeDividerType == "oval"); if (hasShapeDivider) { style += " position: relative;"; } if (hidden) { style += " display: none;"; } Loading @@ -868,6 +938,46 @@ extern "C" { } sect.setAttribute("class", cls.c_str()); // Shape divider: a decorative wave/oval SVG pinned to the // section's top or bottom edge (see member field comment). if (hasShapeDivider) { std::string path; if (shapeDividerType == "wave") { path = "M0,0V46.29c47.79,22.2,103.59,32.17,158,28,70.36-5.37,136.33-33.31,206.8-37.5C438.64,32.43," "512.34,53.67,583,72.05c69.27,18,138.3,24.88,209.4,13.08,36.15-6,69.85-17.84,104.45-29.34" "C989.49,25,1113,44.11,1200,89.66V0Z"; } else { // oval path = "M0,0Q600,100,1200,0V120H0Z"; } std::string dividerStyle = "position: absolute; left: 0; width: 100%; overflow: hidden;" " line-height: 0; pointer-events: none;"; dividerStyle += (shapeDividerPosition == "top") ? " top: 0;" : " bottom: 0;"; if (!shapeDividerHeight.empty()) dividerStyle += " height: " + shapeDividerHeight + ";"; std::string transform; if (shapeDividerPosition == "top") transform += "rotate(180deg) "; if (shapeDividerFlip) transform += "scaleX(-1) "; if (!transform.empty()) dividerStyle += " transform: " + transform + ";"; libhtmlpp::HtmlElement dividerWrap("div"); dividerWrap.setAttribute("class", (responsiveClass + "-shape-divider").c_str()); dividerWrap.setAttribute("style", dividerStyle.c_str()); libhtmlpp::HtmlElement svg("svg"); svg.setAttribute("viewBox", "0 0 1200 120"); svg.setAttribute("preserveAspectRatio", "none"); svg.setAttribute("style", "display: block; width: 100%; height: 100%;"); libhtmlpp::HtmlElement svgPath("path"); svgPath.setAttribute("d", path.c_str()); std::string fill = shapeDividerColor.empty() ? "var(--blogi-bg)" : shapeDividerColor; svgPath.setAttribute("fill", fill.c_str()); svg.appendChild(svgPath); dividerWrap.appendChild(svg); sect.appendChild(dividerWrap); } // Mobile responsive overrides via media query std::string mobileCSS; if (!m_backgroundColor.empty() && m_backgroundColor != "none" && m_backgroundColor != "transparent") { Loading
editor/widgets/section/translations.h +5 −0 Changes for editor/widgets/section/translations.h: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -70,6 +70,11 @@ namespace blogi { namespace webedit { namespace section_i18n { {"Mobile Width", "Breite (Mobil)"}, {"Opacity", "Deckkraft"}, {"Padding", "Innenabstand"}, {"Shape Divider", "Formteiler"}, {"Shape Divider Position", "Formteiler-Position"}, {"Shape Divider Color", "Formteiler-Farbe"}, {"Shape Divider Height", "Formteiler-Höhe"}, {"Shape Divider Flip", "Formteiler spiegeln"}, {"Vertical Align", "Vertikale Ausrichtung"}, {"Width", "Breite"}, }; Loading