From 9059cd28aeee94c856b11b3f5fc795797ecd56bb Mon Sep 17 00:00:00 2001 From: Mandel Olaiya Date: Mon, 30 Mar 2026 18:32:15 +0200 Subject: [PATCH] Format agency site refresh command and nav tags --- .../commands/apply_agency_website_refresh.py | 1078 +++++++++++++++-- .../templatetags/agency_navigation.py | 1 + 2 files changed, 952 insertions(+), 127 deletions(-) diff --git a/mandelstudio/management/commands/apply_agency_website_refresh.py b/mandelstudio/management/commands/apply_agency_website_refresh.py index 60bceeb..dbb9917 100644 --- a/mandelstudio/management/commands/apply_agency_website_refresh.py +++ b/mandelstudio/management/commands/apply_agency_website_refresh.py @@ -6,7 +6,6 @@ from typing import Any from django.core.management.base import BaseCommand from django.db import transaction -from django.utils.text import slugify from wagtail.blocks import StreamValue from wagtail.models import Locale, Page @@ -130,13 +129,25 @@ PAGE_TITLE_MAP = { } COMMON_CTA = { - "nl": {"primary": "Plan een kennismakingsgesprek", "secondary": "Bekijk onze diensten"}, + "nl": { + "primary": "Plan een kennismakingsgesprek", + "secondary": "Bekijk onze diensten", + }, "en": {"primary": "Book an introductory call", "secondary": "View our services"}, "de": {"primary": "Erstgespräch planen", "secondary": "Unsere Leistungen ansehen"}, "fr": {"primary": "Planifier un échange initial", "secondary": "Voir nos services"}, - "es": {"primary": "Planificar una reunión inicial", "secondary": "Ver nuestros servicios"}, - "it": {"primary": "Prenota un colloquio conoscitivo", "secondary": "Scopri i nostri servizi"}, - "pt": {"primary": "Agendar reunião introdutória", "secondary": "Ver os nossos serviços"}, + "es": { + "primary": "Planificar una reunión inicial", + "secondary": "Ver nuestros servicios", + }, + "it": { + "primary": "Prenota un colloquio conoscitivo", + "secondary": "Scopri i nostri servizi", + }, + "pt": { + "primary": "Agendar reunião introdutória", + "secondary": "Ver os nossos serviços", + }, "ru": {"primary": "Запланировать вводный звонок", "secondary": "Посмотреть услуги"}, } @@ -186,57 +197,57 @@ FOOTER_CONTENT = { "about": "

MandelBlog bouwt websites voor dienstverleners, studio’s en kleine teams die professioneel online willen staan zonder template-ruis.

", "links_heading": "Snelle links", "support_heading": "Plan een gesprek", - "support": "

Plan een kennismakingsgesprek
info@mandelblog.com
Bekijk onze diensten

", - "mini": "

Contact - Diensten - Projecten - MandelBlog Studio

", + "support": '

Plan een kennismakingsgesprek
info@mandelblog.com
Bekijk onze diensten

', + "mini": '

Contact - Diensten - Projecten - MandelBlog Studio

', }, "en": { "about": "

MandelBlog builds websites for service businesses, studios and small teams that need a credible online presence without template clutter.

", "links_heading": "Quick links", "support_heading": "Book a call", - "support": "

Book an introductory call
info@mandelblog.com
View our services

", - "mini": "

Contact - Services - Projects - MandelBlog Studio

", + "support": '

Book an introductory call
info@mandelblog.com
View our services

', + "mini": '

Contact - Services - Projects - MandelBlog Studio

', }, "de": { "about": "

MandelBlog entwickelt Websites für Dienstleister, Studios und kleine Teams, die professionell auftreten möchten, ohne Template-Ballast.

", "links_heading": "Schnellzugriff", "support_heading": "Gespräch planen", - "support": "

Erstgespräch planen
info@mandelblog.com
Leistungen ansehen

", - "mini": "

Kontakt - Dienstleistungen - Projekte - MandelBlog Studio

", + "support": '

Erstgespräch planen
info@mandelblog.com
Leistungen ansehen

', + "mini": '

Kontakt - Dienstleistungen - Projekte - MandelBlog Studio

', }, "fr": { "about": "

MandelBlog conçoit des sites pour les sociétés de services, les studios et les petites équipes qui veulent une présence crédible, sans surcharge de template.

", "links_heading": "Accès rapides", "support_heading": "Planifier un échange", - "support": "

Planifier un échange initial
info@mandelblog.com
Voir nos services

", - "mini": "

Contact - Services - Projets - MandelBlog Studio

", + "support": '

Planifier un échange initial
info@mandelblog.com
Voir nos services

', + "mini": '

Contact - Services - Projets - MandelBlog Studio

', }, "es": { "about": "

MandelBlog crea sitios web para empresas de servicios, estudios y pequeños equipos que quieren una presencia creíble sin aspecto de plantilla.

", "links_heading": "Accesos rápidos", "support_heading": "Planificar una reunión", - "support": "

Planificar una reunión inicial
info@mandelblog.com
Ver nuestros servicios

", - "mini": "

Contacto - Servicios - Proyectos - MandelBlog Studio

", + "support": '

Planificar una reunión inicial
info@mandelblog.com
Ver nuestros servicios

', + "mini": '

Contacto - Servicios - Proyectos - MandelBlog Studio

', }, "it": { "about": "

MandelBlog realizza siti per aziende di servizi, studi e piccoli team che vogliono una presenza credibile senza l’effetto template.

", "links_heading": "Link rapidi", "support_heading": "Prenota un colloquio", - "support": "

Prenota un colloquio conoscitivo
info@mandelblog.com
Scopri i nostri servizi

", - "mini": "

Contatto - Servizi - Progetti - MandelBlog Studio

", + "support": '

Prenota un colloquio conoscitivo
info@mandelblog.com
Scopri i nostri servizi

', + "mini": '

Contatto - Servizi - Progetti - MandelBlog Studio

', }, "pt": { "about": "

A MandelBlog cria sites para empresas de serviços, estúdios e pequenas equipas que precisam de uma presença credível sem aparência de template.

", "links_heading": "Acessos rápidos", "support_heading": "Agendar reunião", - "support": "

Agendar reunião introdutória
info@mandelblog.com
Ver os nossos serviços

", - "mini": "

Contacto - Serviços - Projetos - MandelBlog Studio

", + "support": '

Agendar reunião introdutória
info@mandelblog.com
Ver os nossos serviços

', + "mini": '

Contacto - Serviços - Projetos - MandelBlog Studio

', }, "ru": { "about": "

MandelBlog создаёт сайты для сервисных компаний, студий и небольших команд, которым нужен убедительный онлайн-образ без шаблонного шума.

", "links_heading": "Быстрые ссылки", "support_heading": "Назначить звонок", - "support": "

Запланировать вводный звонок
info@mandelblog.com
Посмотреть услуги

", - "mini": "

Контакт - Услуги - Проекты - MandelBlog Studio

", + "support": '

Запланировать вводный звонок
info@mandelblog.com
Посмотреть услуги

', + "mini": '

Контакт - Услуги - Проекты - MandelBlog Studio

', }, } @@ -269,7 +280,9 @@ def clone(blocks: list[dict[str, Any]]) -> list[dict[str, Any]]: return copy.deepcopy(blocks) -def footer_stream_data(locale: str, links: dict[str, str]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: +def footer_stream_data( + locale: str, links: dict[str, str] +) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: cfg = FOOTER_CONTENT[locale] footer = [ block("about_us", {"heading": "MandelBlog Studio", "content": cfg["about"]}), @@ -328,10 +341,50 @@ def nl_home(urls: dict[str, str]) -> list[dict[str, Any]]: "section_title": "Waar MandelBlog op stuurt", "section_subtitle": "

Geen webshopdemo, maar een zakelijke website die klaar is voor aanvragen, vertrouwen en doorontwikkeling.

", "features": [ - item({"icon": "diagram-3", "icon_image": None, "title": "Duidelijke structuur", "description": "

Bezoekers vinden snel de juiste dienst, case of contactroute.

", "link_text": PAGE_TITLE_MAP["process"]["nl"], "link_url": urls["process"], "highlight": "featured"}), - item({"icon": "pencil-square", "icon_image": None, "title": "Zelf te beheren", "description": "

Teksten, beelden en secties beheert u zelf in overzichtelijke blokken.

", "link_text": secondary, "link_url": urls["services"], "highlight": "none"}), - item({"icon": "shield-check", "icon_image": None, "title": "Stabiele technische basis", "description": "

Een schaalbare opzet zonder overbodige complexiteit of template-ruis.

", "link_text": "Bekijk werkwijze", "link_url": urls["process"], "highlight": "none"}), - item({"icon": "graph-up-arrow", "icon_image": None, "title": "Klaar voor doorontwikkeling", "description": "

Later uitbreiden met extra pagina’s, koppelingen of commerce blijft mogelijk.

", "link_text": "Bekijk projecten", "link_url": urls["projects"], "highlight": "none"}), + item( + { + "icon": "diagram-3", + "icon_image": None, + "title": "Duidelijke structuur", + "description": "

Bezoekers vinden snel de juiste dienst, case of contactroute.

", + "link_text": PAGE_TITLE_MAP["process"]["nl"], + "link_url": urls["process"], + "highlight": "featured", + } + ), + item( + { + "icon": "pencil-square", + "icon_image": None, + "title": "Zelf te beheren", + "description": "

Teksten, beelden en secties beheert u zelf in overzichtelijke blokken.

", + "link_text": secondary, + "link_url": urls["services"], + "highlight": "none", + } + ), + item( + { + "icon": "shield-check", + "icon_image": None, + "title": "Stabiele technische basis", + "description": "

Een schaalbare opzet zonder overbodige complexiteit of template-ruis.

", + "link_text": "Bekijk werkwijze", + "link_url": urls["process"], + "highlight": "none", + } + ), + item( + { + "icon": "graph-up-arrow", + "icon_image": None, + "title": "Klaar voor doorontwikkeling", + "description": "

Later uitbreiden met extra pagina’s, koppelingen of commerce blijft mogelijk.

", + "link_text": "Bekijk projecten", + "link_url": urls["projects"], + "highlight": "none", + } + ), ], "columns": "2", }, @@ -347,10 +400,158 @@ def nl_home(urls: dict[str, str]) -> list[dict[str, Any]]: "show_annual_toggle": False, "annual_discount_text": "", "tiers": [ - item({"name": PAGE_TITLE_MAP["starter"]["nl"], "description": "Voor ondernemers die professioneel online willen starten", "price_monthly": None, "price_annual": None, "price_suffix": "", "custom_price_text": "Op offertebasis", "features": [item({"text": "Kernpagina’s en duidelijke navigatie", "included": True, "tooltip": ""}), item({"text": "Editor voor eigen contentbeheer", "included": True, "tooltip": ""}), item({"text": "Mobiel sterke presentatie", "included": True, "tooltip": ""})], "cta_text": primary, "cta_url": urls["contact"], "cta_style": "secondary", "is_featured": False, "featured_label": ""}), - item({"name": PAGE_TITLE_MAP["business"]["nl"], "description": "Voor dienstverleners met meerdere proposities of groeiplannen", "price_monthly": None, "price_annual": None, "price_suffix": "", "custom_price_text": "Op offertebasis", "features": [item({"text": "Meer ruimte voor diensten en cases", "included": True, "tooltip": ""}), item({"text": "Conversiegerichte opbouw", "included": True, "tooltip": ""}), item({"text": "SEO-vriendelijke basis", "included": True, "tooltip": ""})], "cta_text": primary, "cta_url": urls["contact"], "cta_style": "primary", "is_featured": True, "featured_label": "Aanbevolen"}), - item({"name": PAGE_TITLE_MAP["webshop"]["nl"], "description": "Voor organisaties die een zakelijke site willen uitbreiden met online verkoop", "price_monthly": None, "price_annual": None, "price_suffix": "", "custom_price_text": "Op offertebasis", "features": [item({"text": "Productstructuur en checkout", "included": True, "tooltip": ""}), item({"text": "Betalingen en orderverwerking", "included": True, "tooltip": ""}), item({"text": "Schaalbare commerce-opzet", "included": True, "tooltip": ""})], "cta_text": primary, "cta_url": urls["contact"], "cta_style": "secondary", "is_featured": False, "featured_label": ""}), - item({"name": PAGE_TITLE_MAP["support"]["nl"], "description": "Voor teams die onderhoud, technische rust en doorlopende optimalisatie nodig hebben", "price_monthly": None, "price_annual": None, "price_suffix": "", "custom_price_text": "Maandelijks traject", "features": [item({"text": "Updates en onderhoud", "included": True, "tooltip": ""}), item({"text": "Monitoring en technische oplossingen", "included": True, "tooltip": ""}), item({"text": "Doorlopende verbetering", "included": True, "tooltip": ""})], "cta_text": primary, "cta_url": urls["contact"], "cta_style": "secondary", "is_featured": False, "featured_label": ""}), + item( + { + "name": PAGE_TITLE_MAP["starter"]["nl"], + "description": "Voor ondernemers die professioneel online willen starten", + "price_monthly": None, + "price_annual": None, + "price_suffix": "", + "custom_price_text": "Op offertebasis", + "features": [ + item( + { + "text": "Kernpagina’s en duidelijke navigatie", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "Editor voor eigen contentbeheer", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "Mobiel sterke presentatie", + "included": True, + "tooltip": "", + } + ), + ], + "cta_text": primary, + "cta_url": urls["contact"], + "cta_style": "secondary", + "is_featured": False, + "featured_label": "", + } + ), + item( + { + "name": PAGE_TITLE_MAP["business"]["nl"], + "description": "Voor dienstverleners met meerdere proposities of groeiplannen", + "price_monthly": None, + "price_annual": None, + "price_suffix": "", + "custom_price_text": "Op offertebasis", + "features": [ + item( + { + "text": "Meer ruimte voor diensten en cases", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "Conversiegerichte opbouw", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "SEO-vriendelijke basis", + "included": True, + "tooltip": "", + } + ), + ], + "cta_text": primary, + "cta_url": urls["contact"], + "cta_style": "primary", + "is_featured": True, + "featured_label": "Aanbevolen", + } + ), + item( + { + "name": PAGE_TITLE_MAP["webshop"]["nl"], + "description": "Voor organisaties die een zakelijke site willen uitbreiden met online verkoop", + "price_monthly": None, + "price_annual": None, + "price_suffix": "", + "custom_price_text": "Op offertebasis", + "features": [ + item( + { + "text": "Productstructuur en checkout", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "Betalingen en orderverwerking", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "Schaalbare commerce-opzet", + "included": True, + "tooltip": "", + } + ), + ], + "cta_text": primary, + "cta_url": urls["contact"], + "cta_style": "secondary", + "is_featured": False, + "featured_label": "", + } + ), + item( + { + "name": PAGE_TITLE_MAP["support"]["nl"], + "description": "Voor teams die onderhoud, technische rust en doorlopende optimalisatie nodig hebben", + "price_monthly": None, + "price_annual": None, + "price_suffix": "", + "custom_price_text": "Maandelijks traject", + "features": [ + item( + { + "text": "Updates en onderhoud", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "Monitoring en technische oplossingen", + "included": True, + "tooltip": "", + } + ), + item( + { + "text": "Doorlopende verbetering", + "included": True, + "tooltip": "", + } + ), + ], + "cta_text": primary, + "cta_url": urls["contact"], + "cta_style": "secondary", + "is_featured": False, + "featured_label": "", + } + ), ], "footer_text": "

We adviseren welk pakket past bij uw fase, team en doelstelling.

", }, @@ -364,8 +565,26 @@ def nl_home(urls: dict[str, str]) -> list[dict[str, Any]]: "section_title": "Wat opdrachtgevers waarderen", "section_subtitle": "

Kleine teams kiezen voor MandelBlog omdat het traject overzichtelijk blijft en de site daarna echt bruikbaar is.

", "testimonials": [ - item({"quote": "

We kregen in korte tijd een website die eindelijk past bij onze dienstverlening en die we zelf kunnen onderhouden.

", "author_name": "Sanne de Vries", "author_title": "Studio Nova - eigenaar", "author_photo": None, "company_logo": None, "rating": 0}), - item({"quote": "

Het traject was helder, de teksten kregen structuur en onze contactaanvragen lopen nu via één duidelijke route.

", "author_name": "Mark Jansen", "author_title": "Jansen Interieur - medeoprichter", "author_photo": None, "company_logo": None, "rating": 0}), + item( + { + "quote": "

We kregen in korte tijd een website die eindelijk past bij onze dienstverlening en die we zelf kunnen onderhouden.

", + "author_name": "Sanne de Vries", + "author_title": "Studio Nova - eigenaar", + "author_photo": None, + "company_logo": None, + "rating": 0, + } + ), + item( + { + "quote": "

Het traject was helder, de teksten kregen structuur en onze contactaanvragen lopen nu via één duidelijke route.

", + "author_name": "Mark Jansen", + "author_title": "Jansen Interieur - medeoprichter", + "author_photo": None, + "company_logo": None, + "rating": 0, + } + ), ], "customer_logos": [], "aggregate_rating": "", @@ -381,10 +600,34 @@ def nl_home(urls: dict[str, str]) -> list[dict[str, Any]]: "section_title": "Veelgestelde vragen", "section_subtitle": "

We zijn duidelijk over planning, samenwerking en beheer.

", "faqs": [ - item({"question": "Voor welke bedrijven is MandelBlog geschikt?", "answer": "

Voor dienstverleners, studio’s en kleine teams die een professionele site nodig hebben zonder zwaar traject.

", "category": "Algemeen"}), - item({"question": "Kunnen we later uitbreiden?", "answer": "

Ja. We bouwen een structuur waarmee extra pagina’s, talen of koppelingen later logisch aansluiten.

", "category": "Uitbreiding"}), - item({"question": "Beheren we de content zelf?", "answer": "

Ja. De opzet is juist bedoeld zodat uw team pagina’s en blokken zelfstandig kan aanpassen.

", "category": "Beheer"}), - item({"question": "Wat gebeurt er na livegang?", "answer": "

Dan kunt u kiezen voor onderhoud en gerichte doorontwikkeling als dat nodig is.

", "category": "Support"}), + item( + { + "question": "Voor welke bedrijven is MandelBlog geschikt?", + "answer": "

Voor dienstverleners, studio’s en kleine teams die een professionele site nodig hebben zonder zwaar traject.

", + "category": "Algemeen", + } + ), + item( + { + "question": "Kunnen we later uitbreiden?", + "answer": "

Ja. We bouwen een structuur waarmee extra pagina’s, talen of koppelingen later logisch aansluiten.

", + "category": "Uitbreiding", + } + ), + item( + { + "question": "Beheren we de content zelf?", + "answer": "

Ja. De opzet is juist bedoeld zodat uw team pagina’s en blokken zelfstandig kan aanpassen.

", + "category": "Beheer", + } + ), + item( + { + "question": "Wat gebeurt er na livegang?", + "answer": "

Dan kunt u kiezen voor onderhoud en gerichte doorontwikkeling als dat nodig is.

", + "category": "Support", + } + ), ], "show_contact_cta": "card", "contact_cta_text": primary, @@ -422,12 +665,79 @@ def nl_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any] "features_title": "Waar we op letten", "features_sub": "

We werken het liefst voor organisaties die behoefte hebben aan duidelijkheid, snelheid en inhoudelijke regie.

", "features": [ - ("people", "Voor wie we werken", "

Dienstverleners, studio’s en kleine teams met een duidelijke propositie en een praktische planning.

"), - ("diagram-3", "Onze werkwijze", "

We starten met scherpte in doel en inhoud, bouwen met vaste blokken en leveren beheersbaar op.

"), - ("shield-check", "Waarom het anders werkt", "

Geen los template of black box, maar een duidelijke structuur waarmee u zelf verder kunt.

"), - ("person-badge", "Klein team, direct contact", "

U schakelt direct met de mensen die het werk uitvoeren en keuzes vertalen naar de site.

"), + ( + "people", + "Voor wie we werken", + "

Dienstverleners, studio’s en kleine teams met een duidelijke propositie en een praktische planning.

", + ), + ( + "diagram-3", + "Onze werkwijze", + "

We starten met scherpte in doel en inhoud, bouwen met vaste blokken en leveren beheersbaar op.

", + ), + ( + "shield-check", + "Waarom het anders werkt", + "

Geen los template of black box, maar een duidelijke structuur waarmee u zelf verder kunt.

", + ), + ( + "person-badge", + "Klein team, direct contact", + "

U schakelt direct met de mensen die het werk uitvoeren en keuzes vertalen naar de site.

", + ), ], - "extra_block": block("saas_animated_stats", {"layout_width": "container", "background_style": "light", "layout": "cards-grid", "badge": "Werkwijze", "heading": "Onze aanpak in 3 stappen", "subheading": "Kort traject, duidelijke keuzes en daarna een site die voor uw team werkt.", "stats": [item({"value": "1", "prefix": None, "suffix": "", "label": "Kennismaking", "description": "We bepalen doel, inhoud en prioriteiten.", "icon": "chat-square-text", "highlight": False}), item({"value": "2", "prefix": None, "suffix": "", "label": "Uitwerking", "description": "We bouwen de pagina’s en stemmen de inhoud af.", "icon": "layout-text-window", "highlight": False}), item({"value": "3", "prefix": None, "suffix": "", "label": "Oplevering", "description": "U krijgt uitleg, beheer en een duidelijke vervolgstap.", "icon": "rocket", "highlight": False})], "animation_duration": 1800, "animation_easing": "ease-out", "start_on_scroll": True, "show_logos": False, "logos_heading": "", "company_logos": []}), + "extra_block": block( + "saas_animated_stats", + { + "layout_width": "container", + "background_style": "light", + "layout": "cards-grid", + "badge": "Werkwijze", + "heading": "Onze aanpak in 3 stappen", + "subheading": "Kort traject, duidelijke keuzes en daarna een site die voor uw team werkt.", + "stats": [ + item( + { + "value": "1", + "prefix": None, + "suffix": "", + "label": "Kennismaking", + "description": "We bepalen doel, inhoud en prioriteiten.", + "icon": "chat-square-text", + "highlight": False, + } + ), + item( + { + "value": "2", + "prefix": None, + "suffix": "", + "label": "Uitwerking", + "description": "We bouwen de pagina’s en stemmen de inhoud af.", + "icon": "layout-text-window", + "highlight": False, + } + ), + item( + { + "value": "3", + "prefix": None, + "suffix": "", + "label": "Oplevering", + "description": "U krijgt uitleg, beheer en een duidelijke vervolgstap.", + "icon": "rocket", + "highlight": False, + } + ), + ], + "animation_duration": 1800, + "animation_easing": "ease-out", + "start_on_scroll": True, + "show_logos": False, + "logos_heading": "", + "company_logos": [], + }, + ), "cta": "Wilt u weten of onze aanpak past bij uw bedrijf?", }, "services": { @@ -436,10 +746,26 @@ def nl_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any] "features_title": "Wat we leveren", "features_sub": "

Geen losse modules, maar een traject dat aansluit op uw fase, team en doelen.

", "features": [ - ("window", PAGE_TITLE_MAP["starter"]["nl"], "

Voor ondernemers die snel professioneel online willen staan met een heldere basis.

"), - ("briefcase", PAGE_TITLE_MAP["business"]["nl"], "

Voor organisaties met meerdere diensten, cases of een complexere aanbodstructuur.

"), - ("cart-check", PAGE_TITLE_MAP["webshop"]["nl"], "

Voor teams die online verkoop willen toevoegen zonder de grip op techniek te verliezen.

"), - ("wrench-adjustable", PAGE_TITLE_MAP["support"]["nl"], "

Voor organisaties die onderhoud, stabiliteit en doorlopende verbetering nodig hebben.

"), + ( + "window", + PAGE_TITLE_MAP["starter"]["nl"], + "

Voor ondernemers die snel professioneel online willen staan met een heldere basis.

", + ), + ( + "briefcase", + PAGE_TITLE_MAP["business"]["nl"], + "

Voor organisaties met meerdere diensten, cases of een complexere aanbodstructuur.

", + ), + ( + "cart-check", + PAGE_TITLE_MAP["webshop"]["nl"], + "

Voor teams die online verkoop willen toevoegen zonder de grip op techniek te verliezen.

", + ), + ( + "wrench-adjustable", + PAGE_TITLE_MAP["support"]["nl"], + "

Voor organisaties die onderhoud, stabiliteit en doorlopende verbetering nodig hebben.

", + ), ], "extra_block": None, "cta": "Twijfelt u welk pakket past bij uw fase?", @@ -450,9 +776,21 @@ def nl_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any] "features_title": "Wat u in onze projecten terugziet", "features_sub": "

We sturen niet op oppervlakkige effecten, maar op duidelijkheid en bruikbaarheid.

", "features": [ - ("diagram-3", "Heldere pagina-opbouw", "

Bezoekers begrijpen snel waar ze moeten zijn en welke stap logisch volgt.

"), - ("pencil-square", "Eenvoudig beheer", "

Teams kunnen teksten, visuals en pagina’s zelf aanpassen zonder omweg.

"), - ("graph-up-arrow", "Gericht op aanvragen", "

Contact en conversie zijn zichtbaar verwerkt in de structuur en inhoud.

"), + ( + "diagram-3", + "Heldere pagina-opbouw", + "

Bezoekers begrijpen snel waar ze moeten zijn en welke stap logisch volgt.

", + ), + ( + "pencil-square", + "Eenvoudig beheer", + "

Teams kunnen teksten, visuals en pagina’s zelf aanpassen zonder omweg.

", + ), + ( + "graph-up-arrow", + "Gericht op aanvragen", + "

Contact en conversie zijn zichtbaar verwerkt in de structuur en inhoud.

", + ), ], "extra_block": None, "cta": "Wilt u uw volgende project professioneel neerzetten?", @@ -463,11 +801,76 @@ def nl_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any] "features_title": "Waarvoor u contact kunt opnemen", "features_sub": "

Kies de route die past bij uw vraag of traject.

", "features": [ - ("rocket", "Nieuw traject", "

Voor een nieuwe website, herpositionering of complete herbouw.

"), - ("briefcase", "Pakketkeuze", "

Voor advies over welk pakket of welke structuur het beste past.

"), - ("tools", "Onderhoud of uitbreiding", "

Voor technische ondersteuning, uitbreidingen of een vervolgfase na livegang.

"), + ( + "rocket", + "Nieuw traject", + "

Voor een nieuwe website, herpositionering of complete herbouw.

", + ), + ( + "briefcase", + "Pakketkeuze", + "

Voor advies over welk pakket of welke structuur het beste past.

", + ), + ( + "tools", + "Onderhoud of uitbreiding", + "

Voor technische ondersteuning, uitbreidingen of een vervolgfase na livegang.

", + ), ], - "extra_block": block("saas_demo_request", {"layout_width": "container", "background_style": "light", "layout": "split", "section_title": "Vertel kort wat u nodig heeft", "section_subtitle": "

We reageren inhoudelijk en zonder verkooppraat op uw vraag.

", "form_fields": [item({"field_type": "text", "label": "Naam", "placeholder": "Uw naam", "required": True}), item({"field_type": "email", "label": "E-mail", "placeholder": "naam@bedrijf.nl", "required": True}), item({"field_type": "company", "label": "Bedrijf", "placeholder": "Bedrijfsnaam", "required": True}), item({"field_type": "message", "label": "Vraag of project", "placeholder": "Waar zoekt u hulp bij?", "required": False})], "submit_button_text": primary, "form_action_url": urls["contact"], "benefits_title": "Wat u kunt verwachten", "benefits": [item("Reactie binnen 24 uur"), item("Intakegesprek van 15 minuten"), item("Volledig vrijblijvend")], "side_image": 1, "privacy_text": "

We gebruiken uw gegevens alleen voor contact over deze aanvraag.

"}), + "extra_block": block( + "saas_demo_request", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "section_title": "Vertel kort wat u nodig heeft", + "section_subtitle": "

We reageren inhoudelijk en zonder verkooppraat op uw vraag.

", + "form_fields": [ + item( + { + "field_type": "text", + "label": "Naam", + "placeholder": "Uw naam", + "required": True, + } + ), + item( + { + "field_type": "email", + "label": "E-mail", + "placeholder": "naam@bedrijf.nl", + "required": True, + } + ), + item( + { + "field_type": "company", + "label": "Bedrijf", + "placeholder": "Bedrijfsnaam", + "required": True, + } + ), + item( + { + "field_type": "message", + "label": "Vraag of project", + "placeholder": "Waar zoekt u hulp bij?", + "required": False, + } + ), + ], + "submit_button_text": primary, + "form_action_url": urls["contact"], + "benefits_title": "Wat u kunt verwachten", + "benefits": [ + item("Reactie binnen 24 uur"), + item("Intakegesprek van 15 minuten"), + item("Volledig vrijblijvend"), + ], + "side_image": 1, + "privacy_text": "

We gebruiken uw gegevens alleen voor contact over deze aanvraag.

", + }, + ), "cta": "Klaar om een eerste stap te zetten?", }, "process": { @@ -476,10 +879,26 @@ def nl_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any] "features_title": "Zo werken we samen", "features_sub": "

Kort, duidelijk en zonder onnodige ruis.

", "features": [ - ("chat-square-text", "1. Kennismaking", "

We bespreken doel, doelgroep, inhoud en wat u intern wilt kunnen beheren.

"), - ("layout-text-window", "2. Uitwerking", "

We zetten structuur, inhoud en ontwerp om in een duidelijke pagina-opbouw.

"), - ("rocket", "3. Oplevering", "

Na review gaat de site live en zorgen we voor een beheerbare overdracht.

"), - ("graph-up-arrow", "4. Doorontwikkeling", "

Wanneer nodig bouwen we verder op basis van gedrag, vragen en nieuwe plannen.

"), + ( + "chat-square-text", + "1. Kennismaking", + "

We bespreken doel, doelgroep, inhoud en wat u intern wilt kunnen beheren.

", + ), + ( + "layout-text-window", + "2. Uitwerking", + "

We zetten structuur, inhoud en ontwerp om in een duidelijke pagina-opbouw.

", + ), + ( + "rocket", + "3. Oplevering", + "

Na review gaat de site live en zorgen we voor een beheerbare overdracht.

", + ), + ( + "graph-up-arrow", + "4. Doorontwikkeling", + "

Wanneer nodig bouwen we verder op basis van gedrag, vragen en nieuwe plannen.

", + ), ], "extra_block": None, "cta": "Wilt u dit traject ook voor uw website?", @@ -487,13 +906,118 @@ def nl_standard_page(page_key: str, urls: dict[str, str]) -> list[dict[str, Any] } cfg = page_data[page_key] blocks = [ - block("saas_hero_banner", {"layout_width": "container", "background_style": "light", "layout": "split", "badge_text": "MANDELBLOG STUDIO" if page_key != "services" else "DIENSTEN", "badge_url": urls[page_key], "headline": cfg["headline"], "sub_headline": cfg["sub"], "primary_cta_text": primary, "primary_cta_url": urls["contact"], "secondary_cta_text": secondary, "secondary_cta_url": urls["services"], "hero_image": 1 if page_key != "process" else 24, "video_url": "", "stats": [], "customer_logos_title": ""}), - block("saas_features", {"layout_width": "container", "background_style": "light", "layout": "grid", "section_title": cfg["features_title"], "section_subtitle": cfg["features_sub"], "features": [item({"icon": icon, "icon_image": None, "title": title, "description": desc, "link_text": primary if page_key in {"contact", "about"} else secondary, "link_url": urls["contact"] if page_key in {"contact", "about"} else urls["services"], "highlight": "none"}) for icon, title, desc in cfg["features"]], "columns": "2" if len(cfg["features"]) <= 4 else "3"}), + block( + "saas_hero_banner", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "badge_text": "MANDELBLOG STUDIO" + if page_key != "services" + else "DIENSTEN", + "badge_url": urls[page_key], + "headline": cfg["headline"], + "sub_headline": cfg["sub"], + "primary_cta_text": primary, + "primary_cta_url": urls["contact"], + "secondary_cta_text": secondary, + "secondary_cta_url": urls["services"], + "hero_image": 1 if page_key != "process" else 24, + "video_url": "", + "stats": [], + "customer_logos_title": "", + }, + ), + block( + "saas_features", + { + "layout_width": "container", + "background_style": "light", + "layout": "grid", + "section_title": cfg["features_title"], + "section_subtitle": cfg["features_sub"], + "features": [ + item( + { + "icon": icon, + "icon_image": None, + "title": title, + "description": desc, + "link_text": primary + if page_key in {"contact", "about"} + else secondary, + "link_url": urls["contact"] + if page_key in {"contact", "about"} + else urls["services"], + "highlight": "none", + } + ) + for icon, title, desc in cfg["features"] + ], + "columns": "2" if len(cfg["features"]) <= 4 else "3", + }, + ), ] if cfg["extra_block"] is not None: blocks.append(cfg["extra_block"]) - blocks.append(block("saas_faq", {"layout_width": "container", "background_style": "light", "layout": "accordion", "section_title": "Veelgestelde vragen", "section_subtitle": "

We houden het traject helder en praktisch.

", "faqs": [item({"question": "Werken jullie met vaste templates?", "answer": "

Nee. We gebruiken herbruikbare blokken, maar stemmen inhoud en structuur af op uw bedrijf.

", "category": "Werkwijze"}), item({"question": "Kunnen we later uitbreiden?", "answer": "

Ja. De opzet is bedoeld om later door te groeien zonder opnieuw te beginnen.

", "category": "Uitbreiding"}), item({"question": "Beheren we de inhoud zelf?", "answer": "

Ja. Dat is juist een belangrijk uitgangspunt van het platform.

", "category": "Beheer"})], "show_contact_cta": "card", "contact_cta_text": primary, "contact_cta_url": urls["contact"]})) - blocks.append(block("saas_cta_footer", {"layout_width": "container", "background_style": "light", "layout": "banner", "headline": cfg["cta"], "subheadline": "

Plan een kennismakingsgesprek en we laten zien welke route logisch is voor uw bedrijf.

", "primary_cta_text": primary, "primary_cta_url": urls["contact"], "secondary_cta_text": secondary, "secondary_cta_url": urls["services"], "background_image": 1, "side_image": 1, "show_no_credit_card": "with-icon", "no_credit_card_text": "Volledig vrijblijvend"})) + blocks.append( + block( + "saas_faq", + { + "layout_width": "container", + "background_style": "light", + "layout": "accordion", + "section_title": "Veelgestelde vragen", + "section_subtitle": "

We houden het traject helder en praktisch.

", + "faqs": [ + item( + { + "question": "Werken jullie met vaste templates?", + "answer": "

Nee. We gebruiken herbruikbare blokken, maar stemmen inhoud en structuur af op uw bedrijf.

", + "category": "Werkwijze", + } + ), + item( + { + "question": "Kunnen we later uitbreiden?", + "answer": "

Ja. De opzet is bedoeld om later door te groeien zonder opnieuw te beginnen.

", + "category": "Uitbreiding", + } + ), + item( + { + "question": "Beheren we de inhoud zelf?", + "answer": "

Ja. Dat is juist een belangrijk uitgangspunt van het platform.

", + "category": "Beheer", + } + ), + ], + "show_contact_cta": "card", + "contact_cta_text": primary, + "contact_cta_url": urls["contact"], + }, + ) + ) + blocks.append( + block( + "saas_cta_footer", + { + "layout_width": "container", + "background_style": "light", + "layout": "banner", + "headline": cfg["cta"], + "subheadline": "

Plan een kennismakingsgesprek en we laten zien welke route logisch is voor uw bedrijf.

", + "primary_cta_text": primary, + "primary_cta_url": urls["contact"], + "secondary_cta_text": secondary, + "secondary_cta_url": urls["services"], + "background_image": 1, + "side_image": 1, + "show_no_credit_card": "with-icon", + "no_credit_card_text": "Volledig vrijblijvend", + }, + ) + ) return blocks @@ -505,82 +1029,331 @@ def nl_service_page(kind: str, urls: dict[str, str]) -> list[dict[str, Any]]: "title": "Starter-website", "audience": "Voor ondernemers of kleine teams die snel professioneel online willen staan met een duidelijke basis.", "what": [ - ("layout-text-window", "Voor wie is dit?", "

Voor bedrijven met een helder aanbod die snel een professionele eerste indruk willen neerzetten.

"), - ("window", "Wat krijgt u?", "

Kernpagina’s, een logische navigatie en een editor waarmee uw team zelf content kan beheren.

"), - ("graph-up-arrow", "Wat levert het op?", "

Een professionele basis waarmee bezoekers sneller begrijpen wat u doet en hoe ze contact opnemen.

"), + ( + "layout-text-window", + "Voor wie is dit?", + "

Voor bedrijven met een helder aanbod die snel een professionele eerste indruk willen neerzetten.

", + ), + ( + "window", + "Wat krijgt u?", + "

Kernpagina’s, een logische navigatie en een editor waarmee uw team zelf content kan beheren.

", + ), + ( + "graph-up-arrow", + "Wat levert het op?", + "

Een professionele basis waarmee bezoekers sneller begrijpen wat u doet en hoe ze contact opnemen.

", + ), ], "outcomes": [ - ("shield-check", "Heldere online basis", "

Geen overbodige onderdelen, wel een site die vertrouwen geeft.

"), - ("people", "Eenvoudig beheer", "

Uw team kan updates zelf doen zonder afhankelijkheid.

"), - ("rocket", "Snelle livegang", "

Geschikt als eerste professionele stap of als vervanging van een verouderde site.

"), + ( + "shield-check", + "Heldere online basis", + "

Geen overbodige onderdelen, wel een site die vertrouwen geeft.

", + ), + ( + "people", + "Eenvoudig beheer", + "

Uw team kan updates zelf doen zonder afhankelijkheid.

", + ), + ( + "rocket", + "Snelle livegang", + "

Geschikt als eerste professionele stap of als vervanging van een verouderde site.

", + ), + ], + "choose": [ + "U wilt snel professioneel online staan.", + "U heeft vooral kernpagina’s en duidelijke navigatie nodig.", + "U wilt zelf teksten en beelden kunnen aanpassen.", ], - "choose": ["U wilt snel professioneel online staan.", "U heeft vooral kernpagina’s en duidelijke navigatie nodig.", "U wilt zelf teksten en beelden kunnen aanpassen."], "duration": "Gemiddelde oplevering: 2 tot 4 weken", }, "business": { "title": "Zakelijke website", "audience": "Voor dienstverleners en teams die meerdere proposities, cases of funnelstappen helder willen presenteren.", "what": [ - ("briefcase", "Voor wie is dit?", "

Voor organisaties die meer structuur, inhoudelijke diepgang en een sterkere aanvraagroute nodig hebben.

"), - ("layout-text-window", "Wat krijgt u?", "

Meer pagina-opbouw, ruimte voor cases en een SEO-vriendelijke basis die logisch meegroeit.

"), - ("graph-up-arrow", "Wat levert het op?", "

Een site die uw aanbod beter uitlegt en bezoekers gerichter naar contact of aanvraag leidt.

"), + ( + "briefcase", + "Voor wie is dit?", + "

Voor organisaties die meer structuur, inhoudelijke diepgang en een sterkere aanvraagroute nodig hebben.

", + ), + ( + "layout-text-window", + "Wat krijgt u?", + "

Meer pagina-opbouw, ruimte voor cases en een SEO-vriendelijke basis die logisch meegroeit.

", + ), + ( + "graph-up-arrow", + "Wat levert het op?", + "

Een site die uw aanbod beter uitlegt en bezoekers gerichter naar contact of aanvraag leidt.

", + ), ], "outcomes": [ - ("diagram-3", "Meer overzicht", "

Diensten, cases en expertise krijgen elk hun eigen plek.

"), - ("search", "Betere vindbaarheid", "

De opbouw is ingericht voor sterke inhoud en een SEO-vriendelijke basis.

"), - ("people", "Sterkere aanvragen", "

Bezoekers zien sneller welke route en welk aanbod bij hen past.

"), + ( + "diagram-3", + "Meer overzicht", + "

Diensten, cases en expertise krijgen elk hun eigen plek.

", + ), + ( + "search", + "Betere vindbaarheid", + "

De opbouw is ingericht voor sterke inhoud en een SEO-vriendelijke basis.

", + ), + ( + "people", + "Sterkere aanvragen", + "

Bezoekers zien sneller welke route en welk aanbod bij hen past.

", + ), + ], + "choose": [ + "U heeft meerdere diensten of doelgroepen.", + "U wilt cases, expertise en bewijs beter laten zien.", + "U zoekt meer structuur dan een startsite biedt.", ], - "choose": ["U heeft meerdere diensten of doelgroepen.", "U wilt cases, expertise en bewijs beter laten zien.", "U zoekt meer structuur dan een startsite biedt."], "duration": "Gemiddelde oplevering: 2 tot 4 weken", }, "webshop": { "title": "Webshop-implementatie", "audience": "Voor organisaties die online verkoop willen toevoegen zonder in een standaardshop te belanden.", "what": [ - ("cart-check", "Voor wie is dit?", "

Voor bedrijven die hun aanbod online willen verkopen met grip op inhoud, checkout en beheer.

"), - ("credit-card", "Wat krijgt u?", "

Een webshopstructuur met productoverzicht, checkout en een schaalbare basis voor orderverwerking.

"), - ("graph-up-arrow", "Wat levert het op?", "

Een verkoopomgeving die past bij uw merk en niet voelt als een los demo-sjabloon.

"), + ( + "cart-check", + "Voor wie is dit?", + "

Voor bedrijven die hun aanbod online willen verkopen met grip op inhoud, checkout en beheer.

", + ), + ( + "credit-card", + "Wat krijgt u?", + "

Een webshopstructuur met productoverzicht, checkout en een schaalbare basis voor orderverwerking.

", + ), + ( + "graph-up-arrow", + "Wat levert het op?", + "

Een verkoopomgeving die past bij uw merk en niet voelt als een los demo-sjabloon.

", + ), ], "outcomes": [ - ("window", "Betere presentatie", "

Producten en categorieën krijgen een zakelijke, duidelijke opbouw.

"), - ("shield-check", "Stabiele techniek", "

Betalingen en orderverwerking sluiten aan op een beheerbare stack.

"), - ("rocket", "Klaar voor groei", "

De commerce-opzet kan meegroeien met assortiment en processen.

"), + ( + "window", + "Betere presentatie", + "

Producten en categorieën krijgen een zakelijke, duidelijke opbouw.

", + ), + ( + "shield-check", + "Stabiele techniek", + "

Betalingen en orderverwerking sluiten aan op een beheerbare stack.

", + ), + ( + "rocket", + "Klaar voor groei", + "

De commerce-opzet kan meegroeien met assortiment en processen.

", + ), + ], + "choose": [ + "U wilt online verkoop combineren met een zakelijke website.", + "U heeft behoefte aan grip op structuur en techniek.", + "U zoekt geen standaard thema, maar een doordachte implementatie.", ], - "choose": ["U wilt online verkoop combineren met een zakelijke website.", "U heeft behoefte aan grip op structuur en techniek.", "U zoekt geen standaard thema, maar een doordachte implementatie."], "duration": "Gemiddelde oplevering: 3 tot 6 weken", }, "support": { "title": "Onderhoud & groei", "audience": "Voor teams die hun website of webshop stabiel willen houden en gericht willen doorontwikkelen.", "what": [ - ("tools", "Voor wie is dit?", "

Voor organisaties die niet zelf alle techniek willen monitoren, oplossen en plannen.

"), - ("shield-check", "Wat krijgt u?", "

Onderhoud, updates, monitoring en technische oplossingen binnen een vast werkritme.

"), - ("graph-up-arrow", "Wat levert het op?", "

Meer rust, minder technische verrassingen en ruimte om gericht te verbeteren.

"), + ( + "tools", + "Voor wie is dit?", + "

Voor organisaties die niet zelf alle techniek willen monitoren, oplossen en plannen.

", + ), + ( + "shield-check", + "Wat krijgt u?", + "

Onderhoud, updates, monitoring en technische oplossingen binnen een vast werkritme.

", + ), + ( + "graph-up-arrow", + "Wat levert het op?", + "

Meer rust, minder technische verrassingen en ruimte om gericht te verbeteren.

", + ), ], "outcomes": [ - ("activity", "Minder verstoringen", "

Technische issues worden sneller gesignaleerd en opgelost.

"), - ("clipboard-data", "Doorlopende verbetering", "

We werken stap voor stap aan performance, inhoud en conversie.

"), - ("people", "Vast ritme", "

U weet wanneer onderhoud gebeurt en waar prioriteit ligt.

"), + ( + "activity", + "Minder verstoringen", + "

Technische issues worden sneller gesignaleerd en opgelost.

", + ), + ( + "clipboard-data", + "Doorlopende verbetering", + "

We werken stap voor stap aan performance, inhoud en conversie.

", + ), + ( + "people", + "Vast ritme", + "

U weet wanneer onderhoud gebeurt en waar prioriteit ligt.

", + ), + ], + "choose": [ + "U wilt een vaste partner voor technisch onderhoud.", + "Uw site vraagt om kleine verbeteringen in plaats van een volledige herbouw.", + "U wilt sneller kunnen schakelen bij issues of uitbreidingen.", ], - "choose": ["U wilt een vaste partner voor technisch onderhoud.", "Uw site vraagt om kleine verbeteringen in plaats van een volledige herbouw.", "U wilt sneller kunnen schakelen bij issues of uitbreidingen."], "duration": "Reactie binnen 24 uur", }, }[kind] return [ - block("saas_hero_banner", {"layout_width": "container", "background_style": "light", "layout": "split", "badge_text": "PAKKET", "badge_url": urls[kind], "headline": config["title"], "sub_headline": f"

{config['audience']}

", "primary_cta_text": primary, "primary_cta_url": urls['contact'], "secondary_cta_text": secondary, "secondary_cta_url": urls['services'], "hero_image": 23, "video_url": "", "stats": [item({"value": config['duration'], "label": "Doorlooptijd"}), item({"value": "Reactie binnen 24 uur", "label": "Communicatie"}), item({"value": "Volledig vrijblijvend", "label": "Kennismaking"})], "customer_logos_title": ""}), - block("saas_features", {"layout_width": "container", "background_style": "light", "layout": "grid", "section_title": "Wat krijgt u?", "section_subtitle": "

Elk pakket is opgebouwd rond duidelijke keuzes, beheerbaarheid en inhoud die past bij uw bedrijf.

", "features": [item({"icon": icon, "icon_image": None, "title": title, "description": desc, "link_text": secondary, "link_url": urls['services'], "highlight": "none"}) for icon, title, desc in config['what']], "columns": "3"}), - block("saas_features", {"layout_width": "container", "background_style": "light", "layout": "grid", "section_title": "Wat levert het op?", "section_subtitle": "

De waarde zit niet in losse effecten, maar in duidelijkere communicatie en een beter werkende site.

", "features": [item({"icon": icon, "icon_image": None, "title": title, "description": desc, "link_text": primary, "link_url": urls['contact'], "highlight": "none"}) for icon, title, desc in config['outcomes']], "columns": "3"}), - block("saas_demo_request", {"layout_width": "container", "background_style": "light", "layout": "split", "section_title": "Wanneer kiest u dit pakket?", "section_subtitle": "

We adviseren dit pakket wanneer onderstaande punten aansluiten op uw situatie.

", "form_fields": [item({"field_type": "text", "label": "Naam", "placeholder": "Uw naam", "required": True}), item({"field_type": "email", "label": "E-mail", "placeholder": "naam@bedrijf.nl", "required": True}), item({"field_type": "company", "label": "Bedrijf", "placeholder": "Bedrijfsnaam", "required": True}), item({"field_type": "message", "label": "Vraag of context", "placeholder": "Vertel kort waar u nu staat", "required": False})], "submit_button_text": primary, "form_action_url": urls['contact'], "benefits_title": "Dit pakket past wanneer", "benefits": [item(text) for text in config['choose']], "side_image": 23, "privacy_text": "

We gebruiken uw gegevens alleen voor een reactie op uw aanvraag.

"}), - block("saas_cta_footer", {"layout_width": "container", "background_style": "light", "layout": "banner", "headline": f"Wilt u weten of {config['title'].lower()} past bij uw situatie?", "subheadline": "

Plan een kennismakingsgesprek en we adviseren eerlijk welk pakket logisch is.

", "primary_cta_text": primary, "primary_cta_url": urls['contact'], "secondary_cta_text": secondary, "secondary_cta_url": urls['services'], "background_image": 1, "side_image": 1, "show_no_credit_card": "with-icon", "no_credit_card_text": "Volledig vrijblijvend"}), + block( + "saas_hero_banner", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "badge_text": "PAKKET", + "badge_url": urls[kind], + "headline": config["title"], + "sub_headline": f"

{config['audience']}

", + "primary_cta_text": primary, + "primary_cta_url": urls["contact"], + "secondary_cta_text": secondary, + "secondary_cta_url": urls["services"], + "hero_image": 23, + "video_url": "", + "stats": [ + item({"value": config["duration"], "label": "Doorlooptijd"}), + item({"value": "Reactie binnen 24 uur", "label": "Communicatie"}), + item({"value": "Volledig vrijblijvend", "label": "Kennismaking"}), + ], + "customer_logos_title": "", + }, + ), + block( + "saas_features", + { + "layout_width": "container", + "background_style": "light", + "layout": "grid", + "section_title": "Wat krijgt u?", + "section_subtitle": "

Elk pakket is opgebouwd rond duidelijke keuzes, beheerbaarheid en inhoud die past bij uw bedrijf.

", + "features": [ + item( + { + "icon": icon, + "icon_image": None, + "title": title, + "description": desc, + "link_text": secondary, + "link_url": urls["services"], + "highlight": "none", + } + ) + for icon, title, desc in config["what"] + ], + "columns": "3", + }, + ), + block( + "saas_features", + { + "layout_width": "container", + "background_style": "light", + "layout": "grid", + "section_title": "Wat levert het op?", + "section_subtitle": "

De waarde zit niet in losse effecten, maar in duidelijkere communicatie en een beter werkende site.

", + "features": [ + item( + { + "icon": icon, + "icon_image": None, + "title": title, + "description": desc, + "link_text": primary, + "link_url": urls["contact"], + "highlight": "none", + } + ) + for icon, title, desc in config["outcomes"] + ], + "columns": "3", + }, + ), + block( + "saas_demo_request", + { + "layout_width": "container", + "background_style": "light", + "layout": "split", + "section_title": "Wanneer kiest u dit pakket?", + "section_subtitle": "

We adviseren dit pakket wanneer onderstaande punten aansluiten op uw situatie.

", + "form_fields": [ + item( + { + "field_type": "text", + "label": "Naam", + "placeholder": "Uw naam", + "required": True, + } + ), + item( + { + "field_type": "email", + "label": "E-mail", + "placeholder": "naam@bedrijf.nl", + "required": True, + } + ), + item( + { + "field_type": "company", + "label": "Bedrijf", + "placeholder": "Bedrijfsnaam", + "required": True, + } + ), + item( + { + "field_type": "message", + "label": "Vraag of context", + "placeholder": "Vertel kort waar u nu staat", + "required": False, + } + ), + ], + "submit_button_text": primary, + "form_action_url": urls["contact"], + "benefits_title": "Dit pakket past wanneer", + "benefits": [item(text) for text in config["choose"]], + "side_image": 23, + "privacy_text": "

We gebruiken uw gegevens alleen voor een reactie op uw aanvraag.

", + }, + ), + block( + "saas_cta_footer", + { + "layout_width": "container", + "background_style": "light", + "layout": "banner", + "headline": f"Wilt u weten of {config['title'].lower()} past bij uw situatie?", + "subheadline": "

Plan een kennismakingsgesprek en we adviseren eerlijk welk pakket logisch is.

", + "primary_cta_text": primary, + "primary_cta_url": urls["contact"], + "secondary_cta_text": secondary, + "secondary_cta_url": urls["services"], + "background_image": 1, + "side_image": 1, + "show_no_credit_card": "with-icon", + "no_credit_card_text": "Volledig vrijblijvend", + }, + ), ] def nl_body_for(page_key: str, urls: dict[str, str]) -> list[dict[str, Any]]: - if page_key == 'home': + if page_key == "home": return nl_home(urls) - if page_key in {'about', 'services', 'projects', 'contact', 'process'}: + if page_key in {"about", "services", "projects", "contact", "process"}: return nl_standard_page(page_key, urls) - if page_key in {'starter', 'business', 'webshop', 'support'}: + if page_key in {"starter", "business", "webshop", "support"}: return nl_service_page(page_key, urls) raise KeyError(page_key) @@ -589,81 +1362,132 @@ class Command(BaseCommand): help = "Apply MandelBlog agency cleanup content to the main site tree" def add_arguments(self, parser): - parser.add_argument('--apply', action='store_true', help='Persist and publish changes') + parser.add_argument( + "--apply", action="store_true", help="Persist and publish changes" + ) def handle(self, *args, **options): - apply_changes = options['apply'] + apply_changes = options["apply"] with transaction.atomic(): - for locale in Locale.objects.all().order_by('language_code'): + for locale in Locale.objects.all().order_by("language_code"): language_code = locale.language_code translated_pages = {} for key, source_id in SOURCE_PAGE_IDS.items(): source = Page.objects.get(id=source_id) - translated = Page.objects.filter(translation_key=source.translation_key, locale=locale).specific().first() + translated = ( + Page.objects.filter( + translation_key=source.translation_key, locale=locale + ) + .specific() + .first() + ) if translated: translated_pages[key] = translated - urls = {key: page.url for key, page in translated_pages.items() if getattr(page, 'url', None)} - if 'home' not in urls: + urls = { + key: page.url + for key, page in translated_pages.items() + if getattr(page, "url", None) + } + if "home" not in urls: continue for key, page in translated_pages.items(): changed = False title_map = PAGE_TITLE_MAP.get(key) - if title_map and title_map.get(language_code) and page.title != title_map[language_code]: + if ( + title_map + and title_map.get(language_code) + and page.title != title_map[language_code] + ): page.title = title_map[language_code] changed = True - if key == 'ai_search': + if key == "ai_search": if changed and apply_changes: rev = page.save_revision() rev.publish() continue - if hasattr(page, 'body'): + if hasattr(page, "body"): raw_data = list(page.body.raw_data) - if language_code == 'nl' and key in {'home', 'about', 'services', 'projects', 'contact', 'process', 'starter', 'business', 'webshop', 'support'}: - page.body = StreamValue(page.body.stream_block, nl_body_for(key, urls), is_lazy=True) + if language_code == "nl" and key in { + "home", + "about", + "services", + "projects", + "contact", + "process", + "starter", + "business", + "webshop", + "support", + }: + page.body = StreamValue( + page.body.stream_block, + nl_body_for(key, urls), + is_lazy=True, + ) changed = True else: replacements = {} - replacements.update(NL_REPLACEMENTS if language_code == 'nl' else {}) + replacements.update( + NL_REPLACEMENTS if language_code == "nl" else {} + ) if language_code in COMMON_CTA: - primary = COMMON_CTA[language_code]['primary'] - secondary = COMMON_CTA[language_code]['secondary'] - for variant in CTA_VARIANTS.get('nl', []): + primary = COMMON_CTA[language_code]["primary"] + secondary = COMMON_CTA[language_code]["secondary"] + for variant in CTA_VARIANTS.get("nl", []): replacements[variant] = primary - replacements['Bekijk diensten'] = secondary - replacements['Bekijk alle diensten'] = secondary - replacements['Plan kennismaking'] = primary - updated_raw_data = replace_nested(clone(raw_data), replacements) + replacements["Bekijk diensten"] = secondary + replacements["Bekijk alle diensten"] = secondary + replacements["Plan kennismaking"] = primary + updated_raw_data = replace_nested( + clone(raw_data), replacements + ) if updated_raw_data != raw_data: - page.body = StreamValue(page.body.stream_block, updated_raw_data, is_lazy=True) + page.body = StreamValue( + page.body.stream_block, + updated_raw_data, + is_lazy=True, + ) changed = True if changed: - self.stdout.write(f"{language_code}: update {key} -> {page.title}") + self.stdout.write( + f"{language_code}: update {key} -> {page.title}" + ) if apply_changes: rev = page.save_revision() rev.publish() footer_cfg = FOOTER_CONTENT.get(language_code) - footer_obj = LocalizedFooterContent.objects.filter(locale=locale).first() + footer_obj = LocalizedFooterContent.objects.filter( + locale=locale + ).first() if footer_obj and footer_cfg: link_urls = { - 'about': urls.get('about', '/'), - 'services': urls.get('services', '/'), - 'projects': urls.get('projects', '/'), - 'contact': urls.get('contact', '/'), + "about": urls.get("about", "/"), + "services": urls.get("services", "/"), + "projects": urls.get("projects", "/"), + "contact": urls.get("contact", "/"), } - footer_data, mini_data = footer_stream_data(language_code, link_urls) - footer_obj.footer = StreamValue(footer_obj.footer.stream_block, footer_data, is_lazy=True) - footer_obj.mini_footer = StreamValue(footer_obj.mini_footer.stream_block, mini_data, is_lazy=True) + footer_data, mini_data = footer_stream_data( + language_code, link_urls + ) + footer_obj.footer = StreamValue( + footer_obj.footer.stream_block, footer_data, is_lazy=True + ) + footer_obj.mini_footer = StreamValue( + footer_obj.mini_footer.stream_block, mini_data, is_lazy=True + ) self.stdout.write(f"{language_code}: update footer") if apply_changes: footer_obj.save() if not apply_changes: transaction.set_rollback(True) - self.stdout.write(self.style.WARNING('Dry run complete; no changes saved.')) + self.stdout.write( + self.style.WARNING("Dry run complete; no changes saved.") + ) else: - self.stdout.write(self.style.SUCCESS('Agency website refresh applied.')) + self.stdout.write(self.style.SUCCESS("Agency website refresh applied.")) diff --git a/mandelstudio/templatetags/agency_navigation.py b/mandelstudio/templatetags/agency_navigation.py index 7c4dd40..14e25ff 100644 --- a/mandelstudio/templatetags/agency_navigation.py +++ b/mandelstudio/templatetags/agency_navigation.py @@ -76,6 +76,7 @@ CTA_LABELS = { "ru": "Запланировать вводный звонок", } + @register.simple_tag(takes_context=True) def agency_primary_cta(context): request = context.get("request")